图片显示
This commit is contained in:
parent
13ee12a4f9
commit
3f71d2c19f
68
src/components/ImageViewer/index.vue
Normal file
68
src/components/ImageViewer/index.vue
Normal file
@ -0,0 +1,68 @@
|
||||
<template>
|
||||
<teleport to="body">
|
||||
<el-image-viewer
|
||||
v-if="visible"
|
||||
:url-list="urlList"
|
||||
:initial-index="initialIndex"
|
||||
:z-index="9999"
|
||||
@close="handleClose"
|
||||
@switch="handleSwitch"
|
||||
/>
|
||||
</teleport>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="ImageViewer">
|
||||
import { ref, watch } from 'vue';
|
||||
|
||||
interface ImageViewerProps {
|
||||
modelValue: boolean;
|
||||
urlList: string[];
|
||||
initialIndex?: number;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<ImageViewerProps>(), {
|
||||
initialIndex: 0,
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:modelValue', value: boolean): void;
|
||||
(e: 'close'): void;
|
||||
(e: 'switch', index: number): void;
|
||||
}>();
|
||||
|
||||
const visible = ref(props.modelValue);
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(val) => {
|
||||
visible.value = val;
|
||||
}
|
||||
);
|
||||
|
||||
watch(visible, (val) => {
|
||||
emit('update:modelValue', val);
|
||||
});
|
||||
|
||||
const handleClose = () => {
|
||||
visible.value = false;
|
||||
emit('close');
|
||||
};
|
||||
|
||||
const handleSwitch = (index: number) => {
|
||||
emit('switch', index);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
:deep(.el-image-viewer__wrapper) {
|
||||
z-index: 9999 !important;
|
||||
}
|
||||
|
||||
:deep(.el-image-viewer__mask) {
|
||||
z-index: 9999 !important;
|
||||
}
|
||||
|
||||
:deep(.el-image-viewer__canvas) {
|
||||
z-index: 10000 !important;
|
||||
}
|
||||
</style>
|
||||
149
src/components/PreviewImage/index.vue
Normal file
149
src/components/PreviewImage/index.vue
Normal file
@ -0,0 +1,149 @@
|
||||
<template>
|
||||
<div class="preview-image-container" :style="containerStyle">
|
||||
<img
|
||||
:src="src"
|
||||
:style="imageStyle"
|
||||
:alt="alt"
|
||||
class="preview-image"
|
||||
:class="{ 'clickable': !disabled && canPreview }"
|
||||
@click="handleClick"
|
||||
loading="lazy"
|
||||
/>
|
||||
<template v-if="!disabled && canPreview && showZoomIcon">
|
||||
<div class="preview-overlay" @click.stop="handleClick">
|
||||
<el-icon class="zoom-icon">
|
||||
<ZoomIn />
|
||||
</el-icon>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<ImageViewer v-model="viewerVisible" :url-list="urlList" :initial-index="currentIndex" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="PreviewImage">
|
||||
import { ref, computed } from 'vue';
|
||||
import { ZoomIn } from '@element-plus/icons-vue';
|
||||
import ImageViewer from '../ImageViewer/index.vue';
|
||||
|
||||
interface PreviewImageProps {
|
||||
src: string;
|
||||
alt?: string;
|
||||
width?: string | number;
|
||||
height?: string | number;
|
||||
fit?: 'contain' | 'cover' | 'fill' | 'none' | 'scale-down';
|
||||
radius?: string;
|
||||
disabled?: boolean;
|
||||
urlList?: string[];
|
||||
showZoomIcon?: boolean;
|
||||
border?: boolean;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<PreviewImageProps>(), {
|
||||
alt: '',
|
||||
width: '100%',
|
||||
height: 'auto',
|
||||
fit: 'cover',
|
||||
radius: '0',
|
||||
disabled: false,
|
||||
urlList: () => [],
|
||||
showZoomIcon: true,
|
||||
border: false,
|
||||
});
|
||||
|
||||
const viewerVisible = ref(false);
|
||||
const currentIndex = ref(0);
|
||||
|
||||
const canPreview = computed(() => {
|
||||
return props.urlList.length > 0 || props.src;
|
||||
});
|
||||
|
||||
const urlList = computed(() => {
|
||||
if (props.urlList.length > 0) {
|
||||
return props.urlList;
|
||||
}
|
||||
return [props.src];
|
||||
});
|
||||
|
||||
const containerStyle = computed(() => {
|
||||
const style: Record<string, string | number> = {
|
||||
width: typeof props.width === 'number' ? `${props.width}px` : props.width,
|
||||
height: typeof props.height === 'number' ? `${props.height}px` : props.height,
|
||||
borderRadius: props.radius,
|
||||
};
|
||||
if (props.border) {
|
||||
style.border = '1px solid var(--el-border-color)';
|
||||
}
|
||||
return style;
|
||||
});
|
||||
|
||||
const imageStyle = computed(() => {
|
||||
return {
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
objectFit: props.fit,
|
||||
borderRadius: props.radius,
|
||||
};
|
||||
});
|
||||
|
||||
const handleClick = () => {
|
||||
if (props.disabled || !canPreview.value) return;
|
||||
|
||||
if (props.urlList.length > 0) {
|
||||
currentIndex.value = props.urlList.indexOf(props.src);
|
||||
if (currentIndex.value === -1) {
|
||||
currentIndex.value = 0;
|
||||
}
|
||||
} else {
|
||||
currentIndex.value = 0;
|
||||
}
|
||||
|
||||
viewerVisible.value = true;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.preview-image-container {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
overflow: hidden;
|
||||
background-color: var(--el-fill-color-light);
|
||||
}
|
||||
|
||||
.preview-image {
|
||||
display: block;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.preview-image.clickable {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.preview-image-container:hover .preview-image.clickable {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.preview-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease;
|
||||
cursor: pointer;
|
||||
z-index: 10;
|
||||
|
||||
.zoom-icon {
|
||||
font-size: 32px;
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
.preview-image-container:hover .preview-overlay {
|
||||
opacity: 1;
|
||||
}
|
||||
</style>
|
||||
@ -2,12 +2,14 @@ import type { App } from 'vue';
|
||||
import { authDirective } from '/@/directive/authDirective';
|
||||
import { wavesDirective, dragDirective } from '/@/directive/customDirective';
|
||||
import { debounceDirective } from './debounceDirective';
|
||||
import { previewDirective } from './previewDirective';
|
||||
|
||||
/**
|
||||
* 导出指令方法:v-xxx
|
||||
* @methods authDirective 用户权限指令,用法:v-auth
|
||||
* @methods wavesDirective 按钮波浪指令,用法:v-waves
|
||||
* @methods dragDirective 自定义拖动指令,用法:v-drag
|
||||
* @methods previewDirective 图片预览指令,用法:v-preview
|
||||
*/
|
||||
export function directive(app: App) {
|
||||
// 用户权限指令
|
||||
@ -19,4 +21,7 @@ export function directive(app: App) {
|
||||
|
||||
//防抖指令
|
||||
debounceDirective(app);
|
||||
|
||||
// 图片预览指令
|
||||
app.directive('preview', previewDirective);
|
||||
}
|
||||
|
||||
40
src/directive/previewDirective.ts
Normal file
40
src/directive/previewDirective.ts
Normal file
@ -0,0 +1,40 @@
|
||||
import type { Directive, DirectiveBinding } from 'vue';
|
||||
import { useImagePreview } from '../utils/imagePreview';
|
||||
|
||||
const { previewSingleImage } = useImagePreview();
|
||||
|
||||
export const previewDirective: Directive = {
|
||||
mounted(el: HTMLElement, binding: DirectiveBinding) {
|
||||
const imgEl = el.tagName === 'IMG' ? el : el.querySelector('img');
|
||||
if (!imgEl) return;
|
||||
|
||||
const urlList = binding.value?.urlList || [];
|
||||
const disabled = binding.value?.disabled || false;
|
||||
|
||||
if (!disabled) {
|
||||
imgEl.style.cursor = 'pointer';
|
||||
imgEl.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
const src = (imgEl as HTMLImageElement).src;
|
||||
if (urlList.length > 0) {
|
||||
const { showImagePreview } = useImagePreview();
|
||||
const index = urlList.indexOf(src);
|
||||
showImagePreview({
|
||||
urlList,
|
||||
initialIndex: index >= 0 ? index : 0,
|
||||
});
|
||||
} else {
|
||||
previewSingleImage(src);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
unmounted(el: HTMLElement) {
|
||||
const imgEl = el.tagName === 'IMG' ? el : el.querySelector('img');
|
||||
if (!imgEl) return;
|
||||
imgEl.removeEventListener('click', () => {});
|
||||
},
|
||||
};
|
||||
|
||||
export default previewDirective;
|
||||
71
src/utils/imagePreview.ts
Normal file
71
src/utils/imagePreview.ts
Normal file
@ -0,0 +1,71 @@
|
||||
import { createApp, markRaw, h, ref, type App } from 'vue';
|
||||
import ImageViewer from '../components/ImageViewer/index.vue';
|
||||
|
||||
interface ImagePreviewOptions {
|
||||
urlList: string[];
|
||||
initialIndex?: number;
|
||||
}
|
||||
|
||||
let imageViewerInstance: any = null;
|
||||
|
||||
export const useImagePreview = () => {
|
||||
const showImagePreview = (options: ImagePreviewOptions) => {
|
||||
const { urlList, initialIndex = 0 } = options;
|
||||
|
||||
if (!urlList || urlList.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (imageViewerInstance) {
|
||||
imageViewerInstance.unmount();
|
||||
imageViewerInstance = null;
|
||||
}
|
||||
|
||||
const container = document.createElement('div');
|
||||
document.body.appendChild(container);
|
||||
|
||||
const app = createApp({
|
||||
components: { ImageViewer },
|
||||
setup() {
|
||||
const visible = ref(true);
|
||||
|
||||
const handleClose = () => {
|
||||
visible.value = false;
|
||||
setTimeout(() => {
|
||||
app.unmount();
|
||||
if (document.body.contains(container)) {
|
||||
document.body.removeChild(container);
|
||||
}
|
||||
imageViewerInstance = null;
|
||||
}, 300);
|
||||
};
|
||||
|
||||
return () =>
|
||||
h(ImageViewer, {
|
||||
modelValue: visible.value,
|
||||
'onUpdate:modelValue': (val: boolean) => {
|
||||
visible.value = val;
|
||||
if (!val) {
|
||||
handleClose();
|
||||
}
|
||||
},
|
||||
urlList,
|
||||
initialIndex,
|
||||
onClose: handleClose,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
imageViewerInstance = app;
|
||||
app.mount(container);
|
||||
};
|
||||
|
||||
const previewSingleImage = (url: string) => {
|
||||
showImagePreview({ urlList: [url] });
|
||||
};
|
||||
|
||||
return {
|
||||
showImagePreview,
|
||||
previewSingleImage,
|
||||
};
|
||||
};
|
||||
@ -17,20 +17,16 @@
|
||||
<el-row :gutter="15" v-if="state.tableData.data.length > 0" v-loading="state.loading">
|
||||
<el-col :xs="24" :sm="12" :md="8" :lg="6" :xl="4" class="mb15" v-for="v in state.tableData.data" :key="v.id">
|
||||
<div class="img-box">
|
||||
<el-image style="width: 250px; height: 180px" :src="v.url" fit="cover" lazy />
|
||||
<PreviewImage
|
||||
:src="v.url"
|
||||
width="250px"
|
||||
height="180px"
|
||||
fit="cover"
|
||||
radius="8px"
|
||||
:url-list="imageUrlList"
|
||||
:show-zoom-icon="true"
|
||||
/>
|
||||
<div class="operate">
|
||||
<div
|
||||
class="handle-icon"
|
||||
@click="
|
||||
() => {
|
||||
imgViewVisible = true;
|
||||
imageUrl = v.url!;
|
||||
}
|
||||
"
|
||||
>
|
||||
<el-icon><ZoomIn /></el-icon>
|
||||
<span>查看</span>
|
||||
</div>
|
||||
<div class="handle-icon" @click="onDeleteImg(v.id!)">
|
||||
<el-icon><Delete /></el-icon>
|
||||
<span>删除</span>
|
||||
@ -55,18 +51,17 @@
|
||||
</el-pagination>
|
||||
</template>
|
||||
</el-card>
|
||||
<el-image-viewer v-if="imgViewVisible" @close="imgViewVisible = false" :url-list="[imageUrl]" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="blogPictures">
|
||||
import { reactive, onMounted, ref, nextTick } from 'vue';
|
||||
import { reactive, onMounted, ref, computed, nextTick } from 'vue';
|
||||
import PicturesApi from '/@/api/PicturesApi';
|
||||
import { useRoute } from 'vue-router';
|
||||
import type { PicturesPageOutput } from '/@/api/models';
|
||||
// 查看图片
|
||||
const imgViewVisible = ref(false);
|
||||
const imageUrl = ref('');
|
||||
import PreviewImage from '/@/components/PreviewImage/index.vue';
|
||||
import { Delete } from '@element-plus/icons-vue';
|
||||
|
||||
// 定义变量内容
|
||||
const route = useRoute();
|
||||
const state = reactive({
|
||||
@ -83,6 +78,10 @@ const state = reactive({
|
||||
loading: false,
|
||||
});
|
||||
|
||||
const imageUrlList = computed(() => {
|
||||
return state.tableData.data.map((v) => v.url!).filter(Boolean);
|
||||
});
|
||||
|
||||
// 分页点击
|
||||
const onHandleSizeChange = (val: number) => {
|
||||
state.tableData.param.pageSize = val;
|
||||
@ -124,18 +123,7 @@ onMounted(async () => {
|
||||
<style scoped lang="scss">
|
||||
.img-box {
|
||||
position: relative;
|
||||
height: 180px;
|
||||
width: 250px;
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.03);
|
||||
opacity: 1;
|
||||
box-sizing: border-box;
|
||||
:deep(img) {
|
||||
transition: all 0.3s ease;
|
||||
transform: translateZ(0) scale(1.05);
|
||||
}
|
||||
.operate {
|
||||
opacity: 1;
|
||||
}
|
||||
@ -147,28 +135,26 @@ onMounted(async () => {
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
justify-content: flex-end;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
cursor: pointer;
|
||||
background: rgb(0 0 0 / 60%);
|
||||
opacity: 0;
|
||||
transition: var(--el-transition-duration-fast);
|
||||
background: linear-gradient(to top, rgba(0, 0, 0, 0.5) 0%, transparent 50%);
|
||||
.handle-icon {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 6%;
|
||||
padding: 10px;
|
||||
color: aliceblue;
|
||||
.el-icon {
|
||||
margin-bottom: 40%;
|
||||
font-size: 130%;
|
||||
line-height: 130%;
|
||||
font-size: 20px;
|
||||
}
|
||||
span {
|
||||
font-size: 85%;
|
||||
line-height: 85%;
|
||||
font-size: 12px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -12,14 +12,29 @@
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="营业执照:" prop="businessLicenseUrl">
|
||||
<el-upload class="avatar-uploader" action="/api/file/upload" :show-file-list="false"
|
||||
:on-success="handleUploadChange">
|
||||
<div v-if="!formData.businessLicenseUrl" class="upload-placeholder">
|
||||
<i class="el-icon-plus avatar-uploader-icon"></i>
|
||||
<div>上传</div>
|
||||
<div class="image-upload-container">
|
||||
<div class="upload-area">
|
||||
<el-upload class="avatar-uploader" action="/api/file/upload" :show-file-list="false"
|
||||
:on-success="handleUploadChange">
|
||||
<div class="upload-placeholder">
|
||||
<i class="el-icon-plus avatar-uploader-icon"></i>
|
||||
<div>上传</div>
|
||||
</div>
|
||||
</el-upload>
|
||||
</div>
|
||||
<el-image v-else :src="formData.businessLicenseUrl" alt="营业执照" style=" height: 200px;" />
|
||||
</el-upload>
|
||||
<div v-if="formData.businessLicenseUrl" class="preview-area">
|
||||
<PreviewImage
|
||||
:src="formData.businessLicenseUrl"
|
||||
width="100%"
|
||||
height="100%"
|
||||
fit="contain"
|
||||
:show-zoom-icon="true"
|
||||
/>
|
||||
</div>
|
||||
<div v-else class="preview-area preview-empty">
|
||||
<span class="preview-empty-text">预览区域</span>
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="地址:" prop="address">
|
||||
@ -54,6 +69,7 @@ import { ref, reactive, onMounted, computed } from 'vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { merchantcertificationMyCertificationGet, merchantcertificationSubmitCertificationPost } from '/@/api/match/merchantcertification';
|
||||
import { commonSmsSendVerificationCodePost } from '/@/api/match/common';
|
||||
import PreviewImage from '/@/components/PreviewImage/index.vue';
|
||||
|
||||
const formRef = ref(null);
|
||||
|
||||
@ -108,7 +124,7 @@ const beforeUpload = () => {
|
||||
};
|
||||
|
||||
const handleUploadChange = (response) => {
|
||||
formData.businessLicenseUrl = response[0].url;
|
||||
formData.businessLicenseUrl = response?.[0]?.url;
|
||||
};
|
||||
|
||||
const loadData = async () => {
|
||||
@ -190,6 +206,45 @@ onMounted(() => {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.image-upload-container {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.upload-area {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.preview-area {
|
||||
width: 280px;
|
||||
height: 200px;
|
||||
border: 1px solid var(--el-border-color);
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
background-color: var(--el-fill-color-light);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.preview-empty {
|
||||
border: 1px dashed var(--el-border-color);
|
||||
}
|
||||
|
||||
.preview-empty-text {
|
||||
color: var(--el-text-color-secondary);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.avatar-uploader .avatar {
|
||||
width: 178px;
|
||||
height: 178px;
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
.avatar-uploader .el-upload {
|
||||
border: 1px dashed var(--el-border-color);
|
||||
border-radius: 6px;
|
||||
@ -197,23 +252,22 @@ onMounted(() => {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
transition: var(--el-transition-duration-fast);
|
||||
width: 220px;
|
||||
height: 160px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.avatar-uploader .el-upload:hover {
|
||||
border-color: var(--el-color-primary);
|
||||
}
|
||||
|
||||
.avatar-uploader .avatar-uploader-icon {
|
||||
.el-icon.avatar-uploader-icon {
|
||||
font-size: 28px;
|
||||
color: #8c939d;
|
||||
width: 178px;
|
||||
height: 178px;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style scoped>
|
||||
.upload-placeholder {
|
||||
color: #8c939d;
|
||||
text-align: center;
|
||||
|
||||
@ -7,15 +7,40 @@
|
||||
|
||||
<el-form-item label="封面" prop="coverImage">
|
||||
<div v-if="readonly && localFormData.coverImage">
|
||||
<img :src="localFormData.coverImage" class="avatar" />
|
||||
<PreviewImage
|
||||
:src="localFormData.coverImage"
|
||||
width="178px"
|
||||
height="178px"
|
||||
fit="cover"
|
||||
radius="8px"
|
||||
:show-zoom-icon="true"
|
||||
/>
|
||||
</div>
|
||||
<div v-else class="image-upload-container">
|
||||
<div class="upload-area">
|
||||
<el-upload class="avatar-uploader" action="/api/file/upload" :show-file-list="false"
|
||||
:on-success="handleAvatarSuccess" :disabled="readonly">
|
||||
<div class="upload-placeholder">
|
||||
<el-icon class="avatar-uploader-icon">
|
||||
<Plus />
|
||||
</el-icon>
|
||||
<div>上传</div>
|
||||
</div>
|
||||
</el-upload>
|
||||
</div>
|
||||
<div v-if="localFormData.coverImage" class="preview-area">
|
||||
<PreviewImage
|
||||
:src="localFormData.coverImage"
|
||||
width="100%"
|
||||
height="100%"
|
||||
fit="cover"
|
||||
:show-zoom-icon="true"
|
||||
/>
|
||||
</div>
|
||||
<div v-else class="preview-area preview-empty">
|
||||
<span class="preview-empty-text">预览区域</span>
|
||||
</div>
|
||||
</div>
|
||||
<el-upload v-else class="avatar-uploader" action="/api/file/upload" :show-file-list="false"
|
||||
:on-success="handleAvatarSuccess" :disabled="readonly">
|
||||
<img v-if="localFormData.coverImage" :src="localFormData.coverImage" class="avatar" />
|
||||
<el-icon v-else class="avatar-uploader-icon">
|
||||
<Plus />
|
||||
</el-icon>
|
||||
</el-upload>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="赛事类型" prop="eventType">
|
||||
@ -94,6 +119,7 @@ import { ref, watch, onMounted, nextTick } from 'vue';
|
||||
import { Plus } from '@element-plus/icons-vue';
|
||||
import { pointsrulePageGet } from '/@/api/match/pointsrule';
|
||||
import { regionProvincesGet, regionCitiesbyprovinceGet, regionDistrictsbycityGet } from '/@/api/match/area';
|
||||
import PreviewImage from '/@/components/PreviewImage/index.vue';
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: { type: Object, required: true },
|
||||
@ -339,6 +365,42 @@ defineExpose({ validate });
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.image-upload-container {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.upload-area {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.preview-area {
|
||||
width: 178px;
|
||||
height: 178px;
|
||||
border: 1px solid var(--el-border-color);
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
background-color: var(--el-fill-color-light);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.preview-empty {
|
||||
border: 1px dashed var(--el-border-color);
|
||||
}
|
||||
|
||||
.preview-empty-text {
|
||||
color: var(--el-text-color-secondary);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.upload-placeholder {
|
||||
color: #8c939d;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.avatar-uploader .avatar {
|
||||
width: 178px;
|
||||
height: 178px;
|
||||
|
||||
@ -43,7 +43,16 @@
|
||||
</el-tag>
|
||||
</template>
|
||||
<template #paymentProofUrl="scope">
|
||||
<el-image :src="scope.row.paymentProofUrl" style="height: 80px" fit="cover" />
|
||||
<PreviewImage
|
||||
v-if="scope.row.paymentProofUrl"
|
||||
:src="scope.row.paymentProofUrl"
|
||||
width="80px"
|
||||
height="80px"
|
||||
fit="cover"
|
||||
radius="4px"
|
||||
:show-zoom-icon="true"
|
||||
/>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
<template #action="scope">
|
||||
<el-popconfirm title="确认审核完毕吗?" @confirm="onSureOrder(scope.row.id)">
|
||||
@ -163,6 +172,7 @@ import Step2Units from './Steps/Step2Units.vue';
|
||||
import Step3ProjectConfig from './Steps/Step3ProjectConfig.vue';
|
||||
import Step4FeeConfig from './Steps/Step4FeeConfig.vue';
|
||||
import ProTable from '/@/components/ProTable/index.vue';
|
||||
import PreviewImage from '/@/components/PreviewImage/index.vue';
|
||||
|
||||
|
||||
const isShowDialog = ref(false);
|
||||
|
||||
@ -99,7 +99,7 @@ const formRef = ref();
|
||||
const buildEmptyForm = () => ({
|
||||
id: undefined as number | undefined,
|
||||
ruleName: '',
|
||||
participationPoints: 0,
|
||||
participationPoints: undefined as number | undefined,
|
||||
remark: '',
|
||||
details: [] as Array<{ id?: number; startRank: number; endRank: number; points: number }>,
|
||||
});
|
||||
@ -108,7 +108,6 @@ const formData = ref(buildEmptyForm());
|
||||
|
||||
const formRules = ref({
|
||||
ruleName: [{ required: true, message: '请输入规则名称', trigger: 'blur' }],
|
||||
participationPoints: [{ required: true, message: '请输入参与积分', trigger: 'change' }],
|
||||
});
|
||||
|
||||
const columns = ref([
|
||||
@ -139,7 +138,7 @@ const onOpenDialog = async (row?: any) => {
|
||||
formData.value = {
|
||||
id: res.data?.id ?? row.id,
|
||||
ruleName: res.data?.ruleName ?? row.ruleName ?? '',
|
||||
participationPoints: res.data?.participationPoints ?? row.participationPoints ?? 0,
|
||||
participationPoints: res.data?.participationPoints ?? row.participationPoints,
|
||||
remark: res.data?.remark ?? row.remark ?? '',
|
||||
details: (res.data?.details ?? []).map((d: any) => ({
|
||||
id: d.id,
|
||||
|
||||
@ -22,7 +22,16 @@
|
||||
|
||||
</template>
|
||||
<template #idCardImageUrl="scope">
|
||||
<el-image style="width: 100px;" :src="scope.row.idCardImageUrl" fit="cover"></el-image>
|
||||
<PreviewImage
|
||||
v-if="scope.row.idCardImageUrl"
|
||||
:src="scope.row.idCardImageUrl"
|
||||
width="100px"
|
||||
height="100px"
|
||||
fit="cover"
|
||||
radius="4px"
|
||||
:show-zoom-icon="true"
|
||||
/>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
<template #action="scope">
|
||||
<el-button v-auth="'user:general:record'" size="small" text type="primary"
|
||||
@ -76,6 +85,7 @@ import { auths } from '/@/utils/authFunction';
|
||||
import ProTable from '/@/components/ProTable/index.vue';
|
||||
import { appuserPageGet , appuserAuditPost} from '/@/api/match/appuser';
|
||||
import { eventrecordPageGet , eventrecordCertificatesGet} from '/@/api/match/eventRecord';
|
||||
import PreviewImage from '/@/components/PreviewImage/index.vue';
|
||||
|
||||
const initParam = ref({
|
||||
refresh: true,
|
||||
|
||||
@ -15,7 +15,16 @@
|
||||
</el-tag>
|
||||
</template>
|
||||
<template #businessLicenseUrl="scope">
|
||||
<el-image style="width: 100px;" :src="scope.row.businessLicenseUrl" fit="cover"></el-image>
|
||||
<PreviewImage
|
||||
v-if="scope.row.businessLicenseUrl"
|
||||
:src="scope.row.businessLicenseUrl"
|
||||
width="100px"
|
||||
height="100px"
|
||||
fit="cover"
|
||||
radius="4px"
|
||||
:show-zoom-icon="true"
|
||||
/>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
<template #action="scope">
|
||||
<el-button v-auth="'user:general:record'" size="small" text type="primary"
|
||||
@ -89,6 +98,7 @@ import { auths } from '/@/utils/authFunction';
|
||||
import ProTable from '/@/components/ProTable/index.vue';
|
||||
import { eventrecordPageGet , eventrecordCertificatesGet} from '/@/api/match/eventRecord';
|
||||
import { merchantcertificationPageGet, merchantcertificationAuditPost} from '/@/api/match/merchantcertification';
|
||||
import PreviewImage from '/@/components/PreviewImage/index.vue';
|
||||
|
||||
const initParam = ref({
|
||||
refresh: true,
|
||||
|
||||
@ -67,12 +67,12 @@
|
||||
</template>
|
||||
|
||||
<script setup name="sysMenu">
|
||||
import { defineAsyncComponent, ref, reactive, onMounted } from 'vue';
|
||||
import { defineAsyncComponent, ref, reactive, onMounted, computed } from 'vue';
|
||||
import { ElMessage, ElForm } from 'element-plus';
|
||||
import SysMenuApi from '/@/api/SysMenuApi';
|
||||
import { auths } from '/@/utils/authFunction';
|
||||
import ProTable from '/@/components/ProTable/index.vue';
|
||||
import { sysuserPageGet, sysuserUserPost, sysuserUserPut, sysuserDelete } from '/@/api/match/account';
|
||||
import { sysuserPageGet, sysuserUserPost, sysuserUserPut, sysuserDelete, sysuserDetailGet } from '/@/api/match/account';
|
||||
import { sysroleSelectGet } from '/@/api/match/role';
|
||||
|
||||
// 定义表单引用类型
|
||||
@ -99,20 +99,28 @@ const validateConfirmPassword = (rule, value, callback) => {
|
||||
}
|
||||
};
|
||||
|
||||
const formRules = ref({
|
||||
name: [
|
||||
{ required: true, message: '姓名', trigger: 'blur' }
|
||||
],
|
||||
account: [
|
||||
{ required: true, message: '请输入账号', trigger: 'blur' }
|
||||
],
|
||||
password: [
|
||||
{ required: true, message: '请输入密码', trigger: 'blur' },
|
||||
{ min: 6, max: 32, message: '密码长度在 6 到 32 个字符', trigger: 'blur' }
|
||||
],
|
||||
confirmPassword: [
|
||||
{ required: true, validator: validateConfirmPassword, trigger: 'blur' }
|
||||
],
|
||||
const formRules = computed(() => {
|
||||
const rules = {
|
||||
name: [
|
||||
{ required: true, message: '姓名', trigger: 'blur' }
|
||||
],
|
||||
account: [
|
||||
{ required: true, message: '请输入账号', trigger: 'blur' }
|
||||
],
|
||||
};
|
||||
|
||||
// 只有新增时才需要验证密码和确认密码
|
||||
if (!isEdit.value) {
|
||||
rules.password = [
|
||||
{ required: true, message: '请输入密码', trigger: 'blur' },
|
||||
{ min: 6, max: 32, message: '密码长度在 6 到 32 个字符', trigger: 'blur' }
|
||||
];
|
||||
rules.confirmPassword = [
|
||||
{ required: true, validator: validateConfirmPassword, trigger: 'blur' }
|
||||
];
|
||||
}
|
||||
|
||||
return rules;
|
||||
});
|
||||
|
||||
const columns = ref([
|
||||
@ -156,12 +164,23 @@ const columns = ref([
|
||||
|
||||
const isShowDialog = ref(false);
|
||||
const dialogTitle = ref('新增');
|
||||
const isEdit = ref(false);
|
||||
|
||||
const onOpenMenu = async (row) => {
|
||||
|
||||
dialogTitle.value = row ? '编辑' : '新增';
|
||||
isEdit.value = !!row;
|
||||
if (row) {
|
||||
formData.value = row;
|
||||
// 调用详情接口获取完整的用户信息,包括角色
|
||||
const { data } = await sysuserDetailGet({ id: row.id });
|
||||
formData.value = {
|
||||
id: data.id,
|
||||
name: data.name,
|
||||
account: data.account,
|
||||
password: '',
|
||||
confirmPassword: '',
|
||||
roles: data.roles || []
|
||||
};
|
||||
} else {
|
||||
formData.value = {
|
||||
name: '',
|
||||
@ -195,7 +214,9 @@ const onSubmit = () => {
|
||||
try {
|
||||
let res;
|
||||
if (formData.value.id) {
|
||||
res = await sysuserUserPut(formData.value);
|
||||
// 编辑时,只发送需要更新的字段,不发送密码和确认密码
|
||||
const { password, confirmPassword, ...updateData } = formData.value;
|
||||
res = await sysuserUserPut(updateData);
|
||||
} else {
|
||||
res = await sysuserUserPost(formData.value);
|
||||
}
|
||||
|
||||
@ -60,7 +60,7 @@
|
||||
<el-input v-model="teamFormData.name" placeholder="请输入团队名称" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="队员">
|
||||
<el-form-item label="队员" prop="memberList">
|
||||
<div>
|
||||
<el-button type="primary" @click="openMemberDialog">添加队员</el-button>
|
||||
<div style="margin-top:8px">
|
||||
@ -319,8 +319,35 @@ const teamFormData = ref({
|
||||
memberNames: '',
|
||||
});
|
||||
|
||||
const validateMemberList = (rule, value, callback) => {
|
||||
if (!teamFormData.value.memberList || teamFormData.value.memberList.length === 0) {
|
||||
callback(new Error('请至少选择一名队员'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
|
||||
const validateLeaderIds = (rule, value, callback) => {
|
||||
if (!value || value.length === 0) {
|
||||
callback(new Error('请选择领队'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
|
||||
const validateCoachIds = (rule, value, callback) => {
|
||||
if (!value || value.length === 0) {
|
||||
callback(new Error('请选择教练'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
|
||||
const teamFormRules = ref({
|
||||
name: [{ required: true, message: '请输入团队名称', trigger: 'blur' }],
|
||||
memberList: [{ required: true, validator: validateMemberList, trigger: ['change', 'blur'] }],
|
||||
leaderIds: [{ required: true, validator: validateLeaderIds, trigger: ['change', 'blur'] }],
|
||||
coachIds: [{ required: true, validator: validateCoachIds, trigger: ['change', 'blur'] }],
|
||||
});
|
||||
|
||||
// 成员选择弹窗(使用 ProTable 的多选能力)
|
||||
@ -472,10 +499,6 @@ const cancelMemberSelection = () => {
|
||||
// 提交新增团队
|
||||
const onTeamSubmit = () => {
|
||||
if (!teamFormRef.value) return;
|
||||
if(!teamFormData.value.name) return ElMessage.error('请输入团队名称');
|
||||
if(!teamFormData.value.memberIds.length) return ElMessage.error('请选择队员');
|
||||
if(!teamFormData.value.leaderIds.length) return ElMessage.error('请选择领队');
|
||||
if(!teamFormData.value.coachIds.length) return ElMessage.error('请选择教练');
|
||||
teamFormRef.value.validate(async (valid) => {
|
||||
if (!valid) return;
|
||||
try {
|
||||
|
||||
@ -3,8 +3,15 @@
|
||||
<ProTable ref="tableRef" :request-api="usereventregistrationListGet" :columns="columns" :pagination="true"
|
||||
:tool-button="false" :init-param="initParam" row-key="id">
|
||||
<template #coverImage="scope">
|
||||
<el-image v-if="scope.row?.coverImage" style="height: 60px; width: 80px"
|
||||
:src="scope.row.coverImage" fit="cover" />
|
||||
<PreviewImage
|
||||
v-if="scope.row?.coverImage"
|
||||
:src="scope.row.coverImage"
|
||||
width="80px"
|
||||
height="60px"
|
||||
fit="cover"
|
||||
radius="4px"
|
||||
:show-zoom-icon="true"
|
||||
/>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
|
||||
@ -444,6 +451,7 @@ import Step1BasicInfo from '/@/views/match/manage/Steps/Step1BasicInfo.vue';
|
||||
import Step2Units from '/@/views/match/manage/Steps/Step2Units.vue';
|
||||
import Step3ProjectConfig from '/@/views/match/manage/Steps/Step3ProjectConfig.vue';
|
||||
import Step4FeeConfig from '/@/views/match/manage/Steps/Step4FeeConfig.vue';
|
||||
import PreviewImage from '/@/components/PreviewImage/index.vue';
|
||||
|
||||
const tableRef = ref();
|
||||
const initParam = ref({ refresh: true });
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user