소스 검색

增加新的图片组件

Memory_LG 1 주 전
부모
커밋
6a8ad2238d

+ 2 - 1
qmjszx-admin/src/main/java/beilv/web/controller/carinformation/CardAppController.java

@@ -126,7 +126,8 @@ public class CardAppController extends BaseController {
     @PostMapping("/addOrderLog")
     @ResponseBody
     public AjaxResult addOrderLog(@RequestBody VipCardLog vipCardLog) {
-        if (yuanToFen(vipCardLog.getPracticalMoney()) <= 0) {
+
+        if (!"numCard".equals(vipCardLog.getPaymentType()) && !"score".equals(vipCardLog.getPaymentType()) && yuanToFen(vipCardLog.getPracticalMoney()) <= 0) {
             return AjaxResult.error("支付金额无效!");
         }
         String uuid = IdUtils.fastSimpleUUID();

+ 1 - 2
qmjszx-admin/src/main/java/beilv/web/controller/competition/CompetitionController.java

@@ -106,8 +106,7 @@ public class CompetitionController extends BaseController {
     @RequiresPermissions("system:competition:edit")
     @GetMapping("/edit/{id}")
     public String edit(@PathVariable("id") Integer id, ModelMap mmap) {
-        Competition competition = competitionService.selectCompetitionById(id);
-        mmap.put("competition", competition);
+        mmap.put("competition", competitionService.selectCompetitionById(id));
         List<BeilvSite> allToList = siteService.getAllToList();
         mmap.put("siteList",allToList);
         return prefix + "/edit";

+ 0 - 1
qmjszx-admin/src/main/java/beilv/web/controller/system/SysNoticeController.java

@@ -75,7 +75,6 @@ public class SysNoticeController extends BaseController {
     @GetMapping("/edit/{noticeId}")
     public String edit(@PathVariable("noticeId") Long noticeId, ModelMap mmap) {
         SysNotice sysNotice = noticeService.selectNoticeById(noticeId);
-        sysNotice.setFile(sysNotice.getNoticeImg());
         mmap.put("notice", sysNotice);
         return prefix + "/edit";
     }

+ 5 - 0
qmjszx-admin/src/main/resources/static/ajax/libs/bootstrap-fileinput/fileinput.css

@@ -50,6 +50,10 @@ input[type=file].file-loading {
     display: none;
 }
 
+.fileinput-upload-button{
+    display: none !important;
+}
+
 .file-caption .input-group {
     align-items: center;
 }
@@ -522,6 +526,7 @@ input[type=file].file-loading {
     opacity: 0.65;
 }
 
+
 .file-zoom-fullscreen .modal-dialog {
     min-width: 100%;
     margin: 0;

+ 182 - 0
qmjszx-admin/src/main/resources/static/css/image-upload.css

@@ -0,0 +1,182 @@
+/* image-upload.css */
+.image-upload-wrapper {
+    width: 100%;
+}
+
+.upload-container {
+    display: flex;
+    align-items: center;
+    border: 2px dashed #c0c4cc;
+    border-radius: 6px;
+    background-color: #f5f7fa;
+    min-height: 150px;
+    padding: 20px;
+    transition: border-color 0.3s, background-color 0.3s;
+}
+
+.upload-container:hover {
+    border-color: #409eff;
+    background-color: #ecf5ff;
+}
+
+.upload-container.dragover {
+    border-color: #409eff;
+    background-color: #ecf5ff;
+}
+
+.preview-area {
+    flex: 1;
+    display: flex;
+    flex-wrap: wrap;
+    gap: 10px;
+    min-height: 110px;
+    padding-right: 20px;
+    border-right: 1px solid #e0e0e0;
+    margin-right: 20px;
+    order: 1;
+}
+
+.upload-dropzone {
+    position: relative;
+    flex: 1;
+    text-align: center;
+    cursor: pointer;
+    padding: 20px;
+    min-height: 110px;
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+    align-items: center;
+    order: 2;
+}
+
+.upload-content {
+    pointer-events: none;
+}
+
+.upload-icon {
+    font-size: 36px;
+    color: #909399;
+    margin-bottom: 12px;
+}
+
+.upload-title {
+    font-size: 14px;
+    color: #606266;
+    margin-bottom: 6px;
+}
+
+.upload-subtitle {
+    font-size: 12px;
+    color: #909399;
+    line-height: 1.4;
+}
+
+.file-input {
+    position: absolute;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 100%;
+    opacity: 0;
+    cursor: pointer;
+}
+
+.preview-item {
+    position: relative;
+    width: 120px;
+    height: 120px;
+    border: 1px solid #dcdfe6;
+    border-radius: 4px;
+    overflow: hidden;
+    background: #fff;
+    box-shadow: 0 2px 4px rgba(0,0,0,0.1);
+}
+
+.preview-item img {
+    width: 100%;
+    height: 100%;
+    object-fit: cover;
+}
+
+.preview-remove {
+    position: absolute;
+    top: -6px;
+    right: -6px;
+    width: 20px;
+    height: 20px;
+    background: #f56c6c;
+    color: white;
+    border-radius: 50%;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    cursor: pointer;
+    font-size: 12px;
+    z-index: 10;
+    border: 2px solid white;
+}
+
+.preview-remove:hover {
+    background: #f78989;
+}
+
+.preview-info {
+    position: absolute;
+    bottom: 0;
+    left: 0;
+    right: 0;
+    background: rgba(0, 0, 0, 0.6);
+    color: white;
+    padding: 2px 4px;
+    font-size: 10px;
+    text-align: center;
+}
+
+.upload-progress {
+    position: absolute;
+    bottom: 0;
+    left: 0;
+    right: 0;
+    height: 3px;
+    background: #ebeef5;
+}
+
+.upload-progress-bar {
+    height: 100%;
+    background: #409eff;
+    width: 0%;
+    transition: width 0.3s;
+}
+
+.error-message {
+    color: #f56c6c;
+    font-size: 13px;
+    margin-top: 8px;
+    padding: 8px 12px;
+    background: #fef0f0;
+    border-radius: 4px;
+    border-left: 4px solid #f56c6c;
+}
+
+.uploading .upload-title {
+    color: #409eff;
+}
+
+/* 当有图片时调整上传区域样式 */
+.has-preview .upload-dropzone {
+    padding: 10px;
+}
+
+.has-preview .upload-icon {
+    font-size: 24px;
+    margin-bottom: 8px;
+}
+
+.has-preview .upload-title {
+    font-size: 12px;
+}
+
+.has-preview .upload-subtitle {
+    font-size: 10px;
+}

+ 457 - 0
qmjszx-admin/src/main/resources/static/js/image-upload.js

@@ -0,0 +1,457 @@
+// image-upload.js - 修复完善版
+(function($, window, document) {
+    'use strict';
+
+    // 默认配置
+    const DEFAULT_CONFIG = {
+        uploadUrl: '/common/upload',
+        maxFiles: 1,
+        maxSize: 5 * 1024 * 1024, // 5MB
+        allowedTypes: ['image/jpeg', 'image/png', 'image/gif', 'image/jpg', 'image/webp'],
+        previewWidth: 120,
+        previewHeight: 120,
+        inputName: 'images[]',
+        showRemove: true,
+        showError: true,
+        multiple: false,
+        initialFiles: [] // 新增:初始文件数组
+    };
+
+    /**
+     * 图片上传组件构造函数
+     */
+    function ImageUpload(container, options) {
+        this.container = $(container);
+        this.config = $.extend({}, DEFAULT_CONFIG, options);
+        this.files = [];
+        this.initialized = false;
+        this.init();
+    }
+
+    ImageUpload.prototype = {
+        init: function() {
+            this.createHTML();
+            this.bindEvents();
+
+            // 初始化后设置初始文件
+            if (this.config.initialFiles && this.config.initialFiles.length > 0) {
+                setTimeout(() => {
+                    this.setFiles(this.config.initialFiles);
+                }, 50);
+            }
+
+            this.initialized = true;
+        },
+
+        createHTML: function() {
+            const config = this.config;
+
+            const html = `
+                <div class="image-upload-wrapper">
+                    <div class="upload-container">
+                        <div class="preview-area"></div>
+                        <div class="upload-dropzone">
+                            <div class="upload-content">
+                                <i class="fa fa-cloud-upload upload-icon"></i>
+                                <div class="upload-text">
+                                    <div class="upload-title">点击上传图片</div>
+                                    <div class="upload-subtitle">
+                                        支持 ${this.getAllowedTypesString()},最大 ${this.formatFileSize(config.maxSize)}
+                                    </div>
+                                </div>
+                            </div>
+                            <input type="file" class="file-input" 
+                                   ${config.multiple ? 'multiple' : ''}
+                                   accept="${this.getAllowedTypesAccept()}">
+                        </div>
+                    </div>
+                    <div class="hidden-inputs"></div>
+                    <div class="error-message" style="display: none;"></div>
+                </div>
+            `;
+
+            this.container.html(html);
+
+            this.$uploadContainer = this.container.find('.upload-container');
+            this.$dropzone = this.container.find('.upload-dropzone');
+            this.$fileInput = this.container.find('.file-input');
+            this.$previewArea = this.container.find('.preview-area');
+            this.$hiddenInputs = this.container.find('.hidden-inputs');
+            this.$error = this.container.find('.error-message');
+        },
+
+        bindEvents: function() {
+            const self = this;
+
+            // 文件选择变化事件
+            this.$fileInput.on('change', function(e) {
+                const files = Array.from(this.files);
+                self.handleFileSelect(files);
+                // 重置input,以便可以选择相同的文件
+                $(this).val('');
+            });
+
+            // 拖拽事件
+            this.$uploadContainer.on('dragover', function(e) {
+                e.preventDefault();
+                e.stopPropagation();
+                $(this).addClass('dragover');
+            });
+
+            this.$uploadContainer.on('dragleave', function(e) {
+                e.preventDefault();
+                e.stopPropagation();
+                $(this).removeClass('dragover');
+            });
+
+            this.$uploadContainer.on('drop', function(e) {
+                e.preventDefault();
+                e.stopPropagation();
+                $(this).removeClass('dragover');
+
+                const files = Array.from(e.originalEvent.dataTransfer.files);
+                self.handleFileSelect(files);
+            });
+
+            // 删除按钮事件(使用事件委托)
+            this.$previewArea.on('click', '.preview-remove', function(e) {
+                e.stopPropagation();
+                const index = $(this).closest('.preview-item').data('index');
+                self.removeFile(index);
+            });
+        },
+
+        handleFileSelect: function(files) {
+            const config = this.config;
+            const errors = [];
+
+            // 检查文件数量
+            const remainingSlots = config.maxFiles - this.files.length;
+            if (files.length > remainingSlots) {
+                errors.push(`最多只能上传 ${config.maxFiles} 个文件`);
+                files = files.slice(0, remainingSlots);
+            }
+
+            // 验证每个文件
+            files.forEach(file => {
+                const fileErrors = this.validateFile(file);
+                if (fileErrors.length > 0) {
+                    errors.push(`"${file.name}":${fileErrors.join(',')}`);
+                } else {
+                    this.uploadFile(file);
+                }
+            });
+
+            // 显示错误信息
+            if (errors.length > 0 && config.showError) {
+                this.showError(errors.join('<br>'));
+            }
+        },
+
+        validateFile: function(file) {
+            const config = this.config;
+            const errors = [];
+
+            // 检查文件类型
+            const allowedTypes = config.allowedTypes.map(type => type.toLowerCase());
+            const fileType = file.type.toLowerCase();
+
+            if (!allowedTypes.includes(fileType)) {
+                errors.push('文件类型不支持');
+            }
+
+            // 检查文件大小
+            if (file.size > config.maxSize) {
+                errors.push(`文件过大(最大${this.formatFileSize(config.maxSize)})`);
+            }
+
+            return errors;
+        },
+
+        uploadFile: function(file) {
+            const self = this;
+            const config = this.config;
+
+            // 创建预览
+            const previewIndex = this.files.length;
+            this.createPreview(file, previewIndex);
+
+            // 设置上传中状态
+            this.$uploadContainer.addClass('uploading');
+
+            // 创建FormData
+            const formData = new FormData();
+            formData.append('file', file);
+
+            // 发送上传请求
+            $.ajax({
+                url: config.uploadUrl,
+                type: 'POST',
+                data: formData,
+                processData: false,
+                contentType: false,
+                dataType: 'json',
+                xhr: function() {
+                    const xhr = new XMLHttpRequest();
+                    const $progress = self.$previewArea.find(`.preview-item[data-index="${previewIndex}"] .upload-progress-bar`);
+
+                    xhr.upload.addEventListener('progress', function(e) {
+                        if (e.lengthComputable) {
+                            const percent = Math.round((e.loaded / e.total) * 100);
+                            $progress.css('width', percent + '%');
+                        }
+                    }, false);
+
+                    return xhr;
+                },
+                success: function(response) {
+                    self.$uploadContainer.removeClass('uploading');
+
+                    if (response.code === 0) { // 假设0表示成功
+                        // 保存文件信息
+                        self.files.push({
+                            url: response.url,
+                            name: file.name,
+                            size: file.size,
+                            file: file
+                        });
+
+                        // 更新预览
+                        const $previewItem = self.$previewArea.find(`.preview-item[data-index="${previewIndex}"]`);
+                        $previewItem.find('img').attr('src', response.url);
+                        $previewItem.find('.preview-info').text('上传完成');
+
+                        // 添加隐藏字段
+                        self.addHiddenField(response.url, previewIndex);
+
+                        // 如果有图片,添加has-preview类
+                        if (self.files.length > 0) {
+                            self.$uploadContainer.addClass('has-preview');
+                        }
+
+                        // 触发成功事件
+                        $(self.container).trigger('upload.success', {
+                            file: file,
+                            url: response.url,
+                            index: previewIndex
+                        });
+                    } else {
+                        self.removeFile(previewIndex);
+                        self.showError(response.msg || '上传失败');
+                        $(self.container).trigger('upload.error', {
+                            file: file,
+                            error: response.msg || '上传失败'
+                        });
+                    }
+                },
+                error: function(xhr, status, error) {
+                    self.$uploadContainer.removeClass('uploading');
+                    self.removeFile(previewIndex);
+                    self.showError('上传失败:' + (error || '网络错误'));
+                    $(self.container).trigger('upload.error', {
+                        file: file,
+                        error: error || '网络错误'
+                    });
+                }
+            });
+        },
+
+        createPreview: function(file, index) {
+            const reader = new FileReader();
+            const self = this;
+
+            reader.onload = function(e) {
+                const html = `
+                    <div class="preview-item" data-index="${index}">
+                        <img src="${e.target.result}" alt="${file.name}">
+                        <div class="preview-remove" title="删除图片">×</div>
+                        <div class="preview-info">上传中...</div>
+                        <div class="upload-progress">
+                            <div class="upload-progress-bar"></div>
+                        </div>
+                    </div>
+                `;
+
+                self.$previewArea.append(html);
+
+                // 如果有图片,添加has-preview类
+                if (self.files.length >= 0) {
+                    self.$uploadContainer.addClass('has-preview');
+                }
+            };
+
+            reader.readAsDataURL(file);
+        },
+
+        removeFile: function(index) {
+            // 从数组中移除
+            this.files.splice(index, 1);
+
+            // 移除预览
+            this.$previewArea.find(`.preview-item[data-index="${index}"]`).remove();
+
+            // 移除隐藏字段
+            this.$hiddenInputs.find(`input[data-index="${index}"]`).remove();
+
+            // 更新其他元素的索引
+            this.$previewArea.find('.preview-item').each(function(i, el) {
+                const $el = $(el);
+                const currentIndex = parseInt($el.data('index'));
+                if (currentIndex > index) {
+                    $el.data('index', currentIndex - 1);
+                }
+            });
+
+            // 更新隐藏字段的索引
+            this.$hiddenInputs.find('input').each(function(i, el) {
+                const $el = $(el);
+                const currentIndex = parseInt($el.data('index'));
+                if (currentIndex > index) {
+                    $el.data('index', currentIndex - 1);
+                }
+            });
+
+            // 如果没有图片,移除has-preview类
+            if (this.files.length === 0) {
+                this.$uploadContainer.removeClass('has-preview');
+            }
+
+            $(this.container).trigger('file.remove', { index: index });
+        },
+
+        addHiddenField: function(url, index) {
+            const $input = $('<input>', {
+                type: 'hidden',
+                name: this.config.inputName,
+                value: url,
+                'data-index': index
+            });
+
+            this.$hiddenInputs.append($input);
+        },
+
+        showError: function(message) {
+            if (!this.config.showError) return;
+
+            this.$error.html(message).show();
+
+            setTimeout(() => {
+                this.$error.fadeOut();
+            }, 5000);
+        },
+
+        getAllowedTypesString: function() {
+            const extensions = this.config.allowedTypes.map(type => {
+                const parts = type.split('/');
+                return parts[1] ? parts[1].toUpperCase() : '';
+            }).filter(Boolean);
+
+            return extensions.join('、');
+        },
+
+        getAllowedTypesAccept: function() {
+            return this.config.allowedTypes.join(',');
+        },
+
+        formatFileSize: function(bytes) {
+            if (bytes === 0) return '0 B';
+
+            const k = 1024;
+            const sizes = ['B', 'KB', 'MB', 'GB'];
+            const i = Math.floor(Math.log(bytes) / Math.log(k));
+
+            return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
+        },
+
+        // 公共方法
+        getFiles: function() {
+            return this.files.map(file => file.url);
+        },
+
+        getFileObjects: function() {
+            return this.files;
+        },
+
+        clear: function() {
+            this.files = [];
+            this.$previewArea.empty();
+            this.$hiddenInputs.empty();
+            this.$error.hide();
+            this.$uploadContainer.removeClass('has-preview');
+        },
+
+        setFiles: function(urls) {
+            this.clear();
+
+            if (!urls || !Array.isArray(urls)) {
+                urls = [];
+            }
+
+            // 过滤掉空值
+            urls = urls.filter(url => url && url.trim() !== '');
+
+            urls.forEach((url, index) => {
+                this.files.push({
+                    url: url,
+                    name: '已上传图片',
+                    size: 0
+                });
+
+                const html = `
+                    <div class="preview-item" data-index="${index}">
+                        <img src="${url}" alt="图片" onerror="this.src='data:image/svg+xml;utf8,<svg xmlns=\'http://www.w3.org/2000/svg\' width=\'100\' height=\'100\'><rect width=\'100\' height=\'100\' fill=\'%23f0f0f0\'/><text x=\'50\' y=\'50\' font-size=\'12\' text-anchor=\'middle\' fill=\'%23999\'>图片加载失败</text></svg>';">
+                        <div class="preview-remove" title="删除图片">×</div>
+                        <div class="preview-info">已上传</div>
+                    </div>
+                `;
+
+                this.$previewArea.append(html);
+                this.addHiddenField(url, index);
+
+                // 添加has-preview类
+                this.$uploadContainer.addClass('has-preview');
+            });
+
+            // 触发设置完成事件
+            $(this.container).trigger('files.set', { urls: urls });
+
+            return this;
+        },
+
+        destroy: function() {
+            this.$fileInput.off();
+            this.$uploadContainer.off();
+            this.$previewArea.off();
+            this.container.empty();
+            this.container.removeData('imageUpload');
+        }
+    };
+
+    // jQuery插件 - 简化版本
+    $.fn.imageUpload = function(options) {
+        const args = Array.prototype.slice.call(arguments, 1);
+
+        return this.each(function() {
+            const $this = $(this);
+            let instance = $this.data('imageUpload');
+
+            // 如果是方法调用
+            if (typeof options === 'string') {
+                const method = options;
+                if (instance && typeof instance[method] === 'function') {
+                    return instance[method].apply(instance, args);
+                }
+                throw new Error(`方法 "${method}" 不存在`);
+            }
+
+            // 如果是初始化
+            if (!instance) {
+                instance = new ImageUpload(this, options || {});
+                $this.data('imageUpload', instance);
+            }
+
+            return instance;
+        });
+    };
+
+})(jQuery, window, document);

+ 53 - 41
qmjszx-admin/src/main/resources/templates/competition/add.html

@@ -4,7 +4,8 @@
     <th:block th:include="include :: header('新增赛事发布')"/>
     <th:block th:include="include :: datetimepicker-css"/>
     <th:block th:include="include :: summernote-css"/>
-    <th:block th:include="include :: bootstrap-fileinput-css"/>
+    <th:block th:include="include :: image-upload-css"/>
+
     <!-- 自定义 CSS -->
     <style>
         /* 修改文本域的背景颜色和文字颜色 */
@@ -15,13 +16,14 @@
     </style>
 </head>
 <body class="white-bg">
-<div class="wrapper wrapper-content animated fadeInRight ibox-content" >
+<div class="wrapper wrapper-content animated fadeInRight ibox-content">
     <form class="form-horizontal m" id="form-competition-add">
         <div class="col-xs-12">
             <div class="form-group">
                 <label class="col-sm-2 control-label is-required">赛事标题:</label>
                 <div class="col-sm-4">
-                    <input name="competitionTitle" class="form-control" type="text" placeholder="请输入赛事标题" maxlength="50"
+                    <input name="competitionTitle" class="form-control" type="text" placeholder="请输入赛事标题"
+                           maxlength="50"
                            required>
                 </div>
                 <label class="col-sm-2 control-label is-required">赛事类型:</label>
@@ -47,7 +49,7 @@
                         <option th:each="site : ${siteList}" th:value="${site.id}" th:text="${site.name}"></option>
                     </select>
                 </div>
-                    <!--<input name="competitionPlace" class="form-control" type="text" placeholder="请输入场地名称" maxlength="50">-->
+                <!--<input name="competitionPlace" class="form-control" type="text" placeholder="请输入场地名称" maxlength="50">-->
             </div>
         </div>
         <div class="col-xs-12">
@@ -109,8 +111,6 @@
                 </div>
 
 
-
-
                 <label class="col-sm-2 control-label">报名须知:</label>
                 <div class="col-sm-4">
                     <input type="hidden" class="form-control" name="registrationNotes">
@@ -118,18 +118,13 @@
                 </div>
 
 
-
-
-
             </div>
         </div>
         <div class="form-group">
             <label class="col-sm-2 control-label">封面:</label>
             <div class="col-sm-10">
-                <input type="hidden" name="competitionImg">
-                <div class="file-loading">
-                    <input class="form-control file-upload" id="competitionImg" name="file" type="file">
-                </div>
+                <!-- 图片上传组件 -->
+                <div id="image-upload-container"></div>
             </div>
         </div>
     </form>
@@ -137,7 +132,8 @@
 <th:block th:include="include :: footer"/>
 <th:block th:include="include :: datetimepicker-js"/>
 <th:block th:include="include :: summernote-js"/>
-<th:block th:include="include :: bootstrap-fileinput-js"/>
+<th:block th:include="include :: image-upload-js"/>
+
 <script th:inline="javascript">
     var prefix = ctx + "competition"
     $("#form-competition-add").validate({
@@ -171,37 +167,41 @@
         }
     });
 
+    function submitHandler() {
+        var uploadInstance = $('#image-upload-container').data('imageUpload');
 
+        // 检查是否有上传组件实例
+        if (uploadInstance) {
+            var uploadedFiles = uploadInstance.getFiles();
+            console.log('已上传的图片:', uploadedFiles);
 
-    $(".file-upload").each(function (i) {
-        var inputName = this.id;
-        var val = $("input[name='" + inputName + "']").val();
-        // 将已上传的图片路径分割成数组
-        var initialPreview = val ? val.split(',') : [];
+            // 如果需要封面必填
+            if (uploadedFiles.length === 0) {
+                $.modal.alertWarning("请上传封面图片");
+                return false;
+            }
 
-        $(this).fileinput({
-            uploadUrl: ctx + 'common/upload',
-            initialPreviewAsData: true,
-            initialPreview: initialPreview,
-            maxFileCount: 1,
-            allowedFileExtensions: ['jpg', 'png'],
-            maxFileSize: 10240,
-            multiple: false,
-            // required: true
-        }).on('fileuploaded', function (event, data, previewId, index) {
-            var inputName = event.currentTarget.id;
-            var fullUrl = data.response.url; // 获取完整的URL
-            $("input[name='" + inputName + "']").val(fullUrl); // 只保留最新的图片 URL
-        }).on('fileremoved', function (event, key, jqXHR, pd) {
-            var inputName = event.currentTarget.id;
-            // 移除文件后,清空输入框的值
-            $("input[name='" + inputName + "']").val('');
-        });
+            // 将图片URL数组转换为逗号分隔的字符串
+            var imageUrls = uploadedFiles.join(',');
+            console.log('图片URL字符串:', imageUrls);
 
-        $(this).fileinput('_initFileActions');
-    });
+            // 注意:图片上传组件已经通过 addHiddenField 方法添加了隐藏字段
+            // 我们不需要再次添加,只需要确保所有图片都已被收集
+            // 组件会自动处理隐藏字段
+        } else {
+            // 如果没有上传组件实例,直接检查是否有隐藏字段
+            var existingImages = $('#form-competition-add input[name="competitionImg"]').map(function() {
+                return $(this).val();
+            }).get().filter(function(val) {
+                return val && val.trim() !== '';
+            });
+
+            if (existingImages.length === 0) {
+                $.modal.alertWarning("请上传封面图片");
+                return false;
+            }
+        }
 
-    function submitHandler() {
         var applyStartTime = $('#applyStartTime').val()
         var applyEndTime = $('#applyEndTime').val()
         var currentTime = getNowDate() // 当前时间,格式为 YYYY-MM-DD HH:MM
@@ -234,7 +234,7 @@
         autoclose: true
     });
 
-    function getNowDate(){
+    function getNowDate() {
         // 获取当前时间
         var now = new Date();
 
@@ -285,6 +285,18 @@
                 }
             }
         });
+
+
+        // 初始化图片上传组件
+        $('#image-upload-container').imageUpload({
+            uploadUrl: ctx + "common/upload",
+            maxFiles: 1,
+            maxSize: 5 * 1024 * 1024,
+            allowedTypes: ['image/jpeg', 'image/png'],
+            previewWidth: 200,
+            previewHeight: 150,
+            inputName: 'competitionImg'
+        });
     });
 </script>
 </body>

+ 0 - 1
qmjszx-admin/src/main/resources/templates/competition/competition.html

@@ -161,7 +161,6 @@
         $.modal.confirm("确定发布该条" + dataName + "信息吗?<br/>发布后赛事信息无法修改!", function () {
             var url = $.common.isEmpty(id) ? table.options.publishUrl : table.options.publishUrl.replace("{id}", id);
             if (table.options.type == table_type.bootstrapTreeTable) {
-                debugger
                 $.operate.get(url);
             } else {
                 // 将 applyStartTime 转换为 Date 对象

+ 271 - 218
qmjszx-admin/src/main/resources/templates/competition/edit.html

@@ -4,7 +4,7 @@
     <th:block th:include="include :: header('修改赛事发布')" />
     <th:block th:include="include :: datetimepicker-css" />
     <th:block th:include="include :: summernote-css" />
-    <th:block th:include="include :: bootstrap-fileinput-css"/>
+    <th:block th:include="include :: image-upload-css"/>
     <!-- 自定义 CSS -->
     <style>
         /* 修改文本域的背景颜色和文字颜色 */
@@ -15,266 +15,319 @@
     </style>
 </head>
 <body class="white-bg">
-    <div class="wrapper wrapper-content animated fadeInRight ibox-content">
-        <form class="form-horizontal m" id="form-competition-edit" th:object="${competition}">
-            <input name="id" th:field="*{id}" type="hidden">
-            <div class="col-xs-12">
-                <div class="form-group">
-                    <label class="col-sm-2 control-label is-required">赛事标题:</label>
-                    <div class="col-sm-4">
-                        <input name="competitionTitle" th:field="*{competitionTitle}" class="form-control" type="text" maxlength="50" required>
-                    </div>
-                    <label class="col-sm-2 control-label is-required">赛事类型:</label>
-                    <div class="col-sm-4">
-                        <select name="competitionType" class="form-control" th:with="type=${@dict.getType('competition_type')}">
-                            <option th:each="dict : ${type}" th:text="${dict.dictLabel}" th:value="${dict.dictValue}" th:field="*{competitionType}"></option>
-                        </select>
-                    </div>
+<div class="wrapper wrapper-content animated fadeInRight ibox-content">
+    <form class="form-horizontal m" id="form-competition-edit" th:object="${competition}">
+        <input name="id" th:field="*{id}" type="hidden">
+        <div class="col-xs-12">
+            <div class="form-group">
+                <label class="col-sm-2 control-label is-required">赛事标题:</label>
+                <div class="col-sm-4">
+                    <input name="competitionTitle" th:field="*{competitionTitle}" class="form-control" type="text" maxlength="50" required>
+                </div>
+                <label class="col-sm-2 control-label is-required">赛事类型:</label>
+                <div class="col-sm-4">
+                    <select name="competitionType" class="form-control" th:with="type=${@dict.getType('competition_type')}">
+                        <option th:each="dict : ${type}" th:text="${dict.dictLabel}" th:value="${dict.dictValue}" th:field="*{competitionType}"></option>
+                    </select>
                 </div>
             </div>
-            <div class="col-xs-12">
-                <div class="form-group">
-                    <label class="col-sm-2 control-label is-required">最大人数/团队数:</label>
-                    <div class="col-sm-4">
-                        <input name="teamMax" th:field="*{teamMax}" class="form-control" type="text" required>
-                    </div>
-                    <label class="col-sm-2 control-label">场地:</label>
-                    <div class="col-sm-4">
-                        <select name="competitionPlace" class="form-control" th:value="${competition.competitionPlace}">
-                            <option th:each="site : ${siteList}"
-                                    th:value="${site.id}"
-                                    th:text="${site.name}"
-                                    th:selected="${site.name == competition.competitionPlace}">
-                            </option>
-                        </select>
-                    </div>
+        </div>
+        <div class="col-xs-12">
+            <div class="form-group">
+                <label class="col-sm-2 control-label is-required">最大人数/团队数:</label>
+                <div class="col-sm-4">
+                    <input name="teamMax" th:field="*{teamMax}" class="form-control" type="text" required>
+                </div>
+                <label class="col-sm-2 control-label">场地:</label>
+                <div class="col-sm-4">
+                    <select name="competitionPlace" class="form-control" th:value="${competition.competitionPlace}">
+                        <option th:each="site : ${siteList}"
+                                th:value="${site.id}"
+                                th:text="${site.name}"
+                                th:selected="${site.name == competition.competitionPlace}">
+                        </option>
+                    </select>
                 </div>
             </div>
-            <div class="col-xs-12">
-                <div class="form-group">
-                    <label class="col-sm-2 control-label is-required">报名费用:</label>
-                    <div class="col-sm-4">
-                        <input name="competitionExpense" th:field="*{competitionExpense}" class="form-control" type="text" required>
-                    </div>
-                    <label class="col-sm-2 control-label is-required">观看费用:</label>
-                    <div class="col-sm-4">
-                        <input name="viewingTicket" th:field="*{viewingTicket}" class="form-control" type="text" required>
-                    </div>
+        </div>
+        <div class="col-xs-12">
+            <div class="form-group">
+                <label class="col-sm-2 control-label is-required">报名费用:</label>
+                <div class="col-sm-4">
+                    <input name="competitionExpense" th:field="*{competitionExpense}" class="form-control" type="text" required>
+                </div>
+                <label class="col-sm-2 control-label is-required">观看费用:</label>
+                <div class="col-sm-4">
+                    <input name="viewingTicket" th:field="*{viewingTicket}" class="form-control" type="text" required>
                 </div>
             </div>
-            <div class="col-xs-12">
-                <div class="form-group">
-                    <label class="col-sm-2 control-label is-required">赛事开始时间:</label>
-                    <div class="col-sm-4">
-                        <div class="input-group date">
-                            <span class="input-group-addon"><i class="fa fa-calendar"></i></span>
-                            <input name="applyStartTime" th:value="${#dates.format(competition.applyStartTime, 'yyyy-MM-dd HH:mm')}" type="text" class="form-control" id="applyStartTime" placeholder="年-月-日 时:分" required>
-                        </div>
-                    </div>
-                    <label class="col-sm-2 control-label is-required">赛事结束时间:</label>
-                    <div class="col-sm-4">
-                        <div class="input-group date">
-                            <span class="input-group-addon"><i class="fa fa-calendar"></i></span>
-                            <input name="applyEndTime" th:value="${#dates.format(competition.applyEndTime, 'yyyy-MM-dd HH:mm')}" type="text" class="form-control" id="applyEndTime" placeholder="年-月-日 时:分" required>
-                        </div>
+        </div>
+        <div class="col-xs-12">
+            <div class="form-group">
+                <label class="col-sm-2 control-label is-required">赛事开始时间:</label>
+                <div class="col-sm-4">
+                    <div class="input-group date">
+                        <span class="input-group-addon"><i class="fa fa-calendar"></i></span>
+                        <input name="applyStartTime" th:value="${#dates.format(competition.applyStartTime, 'yyyy-MM-dd HH:mm')}" type="text" class="form-control" id="applyStartTime" placeholder="年-月-日 时:分" required>
                     </div>
                 </div>
-            </div>
-            <div class="col-xs-12">
-                <div class="form-group">
-                    <label class="col-sm-2 control-label is-required">取消报名截至时间:</label>
-                    <div class="col-sm-4">
-                        <select th:field="*{applyBeforeTime}" name="applyBeforeTime" class="form-control">
-                            <option value="1">赛事开始前1小时</option>
-                            <option value="2">赛事开始前2小时</option>
-                            <option value="3">赛事开始前3小时</option>
-                            <option value="6">赛事开始前6小时</option>
-                            <option value="12">赛事开始前12小时</option>
-                            <option value="24">赛事开始前24小时</option>
-                            <option value="48">赛事开始前48小时</option>
-                        </select>
+                <label class="col-sm-2 control-label is-required">赛事结束时间:</label>
+                <div class="col-sm-4">
+                    <div class="input-group date">
+                        <span class="input-group-addon"><i class="fa fa-calendar"></i></span>
+                        <input name="applyEndTime" th:value="${#dates.format(competition.applyEndTime, 'yyyy-MM-dd HH:mm')}" type="text" class="form-control" id="applyEndTime" placeholder="年-月-日 时:分" required>
                     </div>
                 </div>
             </div>
-
-            <div class="col-xs-12">
-                <div class="form-group">
-                    <label class="col-sm-2 control-label is-required">赛事详情:</label>
-                    <div class="col-sm-4">
-                        <input type="hidden" class="form-control" th:field="*{competitionDetails}" required>
-                        <div class="summernote" id="competitionDetails"></div>
-                    </div>
-                    <label class="col-sm-2 control-label">报名须知:</label>
-                    <div class="col-sm-4">
-                        <input type="hidden" class="form-control" th:field="*{registrationNotes}">
-                        <div class="summernote" id="registrationNotes"></div>
-                    </div>
+        </div>
+        <div class="col-xs-12">
+            <div class="form-group">
+                <label class="col-sm-2 control-label is-required">取消报名截至时间:</label>
+                <div class="col-sm-4">
+                    <select th:field="*{applyBeforeTime}" name="applyBeforeTime" class="form-control">
+                        <option value="1">赛事开始前1小时</option>
+                        <option value="2">赛事开始前2小时</option>
+                        <option value="3">赛事开始前3小时</option>
+                        <option value="6">赛事开始前6小时</option>
+                        <option value="12">赛事开始前12小时</option>
+                        <option value="24">赛事开始前24小时</option>
+                        <option value="48">赛事开始前48小时</option>
+                    </select>
                 </div>
             </div>
+        </div>
+
+        <div class="col-xs-12">
             <div class="form-group">
-                <label class="col-sm-2 control-label">图片:</label>
-                <div class="col-sm-10">
-                    <input type="hidden" name="noticeImg" th:field="*{competitionImg}">
-                    <div class="file-loading">
-                        <input class="form-control file-upload" id="competitionImg" name="file" type="file" >
-                    </div>
+                <label class="col-sm-2 control-label is-required">赛事详情:</label>
+                <div class="col-sm-4">
+                    <input type="hidden" class="form-control" th:field="*{competitionDetails}" required>
+                    <div class="summernote" id="competitionDetails"></div>
+                </div>
+                <label class="col-sm-2 control-label">报名须知:</label>
+                <div class="col-sm-4">
+                    <input type="hidden" class="form-control" th:field="*{registrationNotes}">
+                    <div class="summernote" id="registrationNotes"></div>
                 </div>
             </div>
-        </form>
-    </div>
-    <th:block th:include="include :: footer" />
-    <th:block th:include="include :: datetimepicker-js" />
-    <th:block th:include="include :: summernote-js" />
-    <th:block th:include="include :: bootstrap-fileinput-js"/>
-    <script th:inline="javascript">
-        var prefix = ctx + "competition";
-        $("#form-competition-edit").validate({
-            focusCleanup: true,
-            rules: {
-                teamMax: {
-                    digits: true
-                },
-                competitionExpense: {
-                    required: true, // 必填
-                    digits: true, // 必须为整数
-                    min: 1 // 最小值为1,确保为正整数
-                },
-                viewingTicket: {
-                    required: true, // 必填
-                    digits: true, // 必须为整数
-                    min: 1 // 最小值为1,确保为正整数
-                }
+        </div>
+        <div class="form-group">
+            <label class="col-sm-2 control-label">封面:</label>
+            <div class="col-sm-10">
+                <div id="image-upload-container"></div>
+            </div>
+        </div>
+    </form>
+</div>
+<th:block th:include="include :: footer" />
+<th:block th:include="include :: datetimepicker-js" />
+<th:block th:include="include :: summernote-js" />
+<th:block th:include="include :: image-upload-js"/>
+<script th:inline="javascript">
+    var prefix = ctx + "competition";
+    $("#form-competition-edit").validate({
+        focusCleanup: true,
+        rules: {
+            teamMax: {
+                digits: true
             },
-            messages: {
-                competitionExpense: {
-                    required: "报名费用不能为空",
-                    digits: "报名费用必须为整数",
-                    min: "报名费用必须大于0"
-                },
-                viewingTicket: {
-                    required: "观看费用不能为空",
-                    digits: "观看费用必须为整数",
-                    min: "观看费用必须大于0"
-                }
+            competitionExpense: {
+                required: true, // 必填
+                digits: true, // 必须为整数
+                min: 1 // 最小值为1,确保为正整数
+            },
+            viewingTicket: {
+                required: true, // 必填
+                digits: true, // 必须为整数
+                min: 1 // 最小值为1,确保为正整数
             }
-        });
+            // 注意:这里不再验证 hidden 字段,由组件自己管理
+        },
+        messages: {
+            competitionExpense: {
+                required: "报名费用不能为空",
+                digits: "报名费用必须为整数",
+                min: "报名费用必须大于0"
+            },
+            viewingTicket: {
+                required: "观看费用不能为空",
+                digits: "观看费用必须为整数",
+                min: "观看费用必须大于0"
+            }
+        }
+    });
 
-        function submitHandler() {
+    function submitHandler() {
+        var applyStartTime = $('#applyStartTime').val()
+        var applyEndTime = $('#applyEndTime').val()
+        var currentTime = getNowDate(); // 当前时间,格式为 YYYY-MM-DD HH:MM
 
-            var applyStartTime = $('#applyStartTime').val()
-            var applyEndTime = $('#applyEndTime').val()
-            var currentTime = getNowDate(); // 当前时间,格式为 YYYY-MM-DD HH:MM
+        if ($.validate.form()) {
+            // 获取上传组件实例
+            var uploadInstance = $('#image-upload-container').data('imageUpload');
 
-            if ($.validate.form()) {
+            // 检查是否有上传的图片
+            if (uploadInstance) {
+                var uploadedFiles = uploadInstance.getFiles();
+                console.log('已上传的图片:', uploadedFiles);
 
-                // 检查赛事开始时间不能小于当前时间
-                if (applyStartTime < currentTime) {
-                    $.modal.alertWarning("赛事开始时间不能小于当前时间");
+                // 检查封面图片是否上传
+                if (uploadedFiles.length === 0) {
+                    $.modal.alertWarning("请上传封面图片");
                     return false;
                 }
+                // 组件会自动更新隐藏字段,我们不需要额外处理
+            } else {
+                // 如果没有上传组件实例,检查是否有隐藏字段
+                var existingImages = $('#form-competition-edit input[name="competitionImg"]').map(function() {
+                    return $(this).val();
+                }).get().filter(function(val) {
+                    return val && val.trim() !== '';
+                });
 
-                // 检查赛事结束时间不能小于赛事开始时间
-                if (applyEndTime < applyStartTime) {
-                    $.modal.alertWarning("赛事结束时间不能小于赛事开始时间");
+                if (existingImages.length === 0) {
+                    $.modal.alertWarning("请上传封面图片");
                     return false;
                 }
+            }
 
-                $.operate.save(prefix + "/edit", $('#form-competition-edit').serialize());
+            // 检查赛事开始时间不能小于当前时间
+            if (applyStartTime < currentTime) {
+                $.modal.alertWarning("赛事开始时间不能小于当前时间");
+                return false;
             }
-        }
 
-        $("input[name='applyStartTime']").datetimepicker({
-            format: "yyyy-mm-dd hh:ii",
-            autoclose: true
-        });
+            // 检查赛事结束时间不能小于赛事开始时间
+            if (applyEndTime < applyStartTime) {
+                $.modal.alertWarning("赛事结束时间不能小于赛事开始时间");
+                return false;
+            }
 
-        $("input[name='applyEndTime']").datetimepicker({
-            format: "yyyy-mm-dd hh:ii",
-            autoclose: true
-        });
+            $.operate.save(prefix + "/edit", $('#form-competition-edit').serialize());
+        }
+    }
 
-        function getNowDate(){
-            // 获取当前时间
-            var now = new Date();
+    $("input[name='applyStartTime']").datetimepicker({
+        format: "yyyy-mm-dd hh:ii",
+        autoclose: true
+    });
 
-            // 格式化日期和时间
-            var year = now.getFullYear(); // 获取年份
-            var month = ("0" + (now.getMonth() + 1)).slice(-2); // 获取月份,加1是因为 getMonth() 返回的月份是从0开始的
-            var day = ("0" + now.getDate()).slice(-2); // 获取日期
-            var hours = ("0" + now.getHours()).slice(-2); // 获取小时
-            var minutes = ("0" + now.getMinutes()).slice(-2); // 获取分钟
+    $("input[name='applyEndTime']").datetimepicker({
+        format: "yyyy-mm-dd hh:ii",
+        autoclose: true
+    });
 
-            // 拼接成所需的格式
-            return year + "-" + month + "-" + day + " " + hours + ":" + minutes;
+    function getNowDate(){
+        // 获取当前时间
+        var now = new Date();
 
-        }
+        // 格式化日期和时间
+        var year = now.getFullYear(); // 获取年份
+        var month = ("0" + (now.getMonth() + 1)).slice(-2); // 获取月份,加1是因为 getMonth() 返回的月份是从0开始的
+        var day = ("0" + now.getDate()).slice(-2); // 获取日期
+        var hours = ("0" + now.getHours()).slice(-2); // 获取小时
+        var minutes = ("0" + now.getMinutes()).slice(-2); // 获取分钟
+
+        // 拼接成所需的格式
+        return year + "-" + month + "-" + day + " " + hours + ":" + minutes;
 
-        $(function() {
+    }
 
-            $('.summernote').each(function(i) {
-                $('#' + this.id).summernote({
-                    lang: 'zh-CN',
-                    dialogsInBody: true,
-                    height: 400,
-                    callbacks: {
-                        onChange: function(contents, $edittable) {
-                            $("input[name='" + this.id + "']").val(contents);
-                        },
-                        onImageUpload: function(files) {
-                            var obj = this;
-                            var data = new FormData();
-                            data.append("file", files[0]);
-                            $.ajax({
-                                type: "post",
-                                url: ctx + "common/upload",
-                                data: data,
-                                cache: false,
-                                contentType: false,
-                                processData: false,
-                                dataType: 'json',
-                                success: function(result) {
-                                    if (result.code == web_status.SUCCESS) {
-                                        $('#' + obj.id).summernote('insertImage', result.url);
-                                    } else {
-                                        $.modal.alertError(result.msg);
-                                    }
-                                },
-                                error: function(error) {
-                                    $.modal.alertWarning("图片上传失败。");
+    $(function() {
+        // 初始化 summernote 编辑器
+        $('.summernote').each(function(i) {
+            $('#' + this.id).summernote({
+                lang: 'zh-CN',
+                dialogsInBody: true,
+                height: 400,
+                callbacks: {
+                    onChange: function(contents, $edittable) {
+                        $("input[name='" + this.id + "']").val(contents);
+                    },
+                    onImageUpload: function(files) {
+                        var obj = this;
+                        var data = new FormData();
+                        data.append("file", files[0]);
+                        $.ajax({
+                            type: "post",
+                            url: ctx + "common/upload",
+                            data: data,
+                            cache: false,
+                            contentType: false,
+                            processData: false,
+                            dataType: 'json',
+                            success: function(result) {
+                                if (result.code == web_status.SUCCESS) {
+                                    $('#' + obj.id).summernote('insertImage', result.url);
+                                } else {
+                                    $.modal.alertError(result.msg);
                                 }
-                            });
-                        }
+                            },
+                            error: function(error) {
+                                $.modal.alertWarning("图片上传失败。");
+                            }
+                        });
                     }
-                });
-                var content = $("input[name='" + this.id + "']").val();
-                $('#' + this.id).summernote('code', content);
-            })
+                }
+            });
+            var content = $("input[name='" + this.id + "']").val();
+            $('#' + this.id).summernote('code', content);
         });
 
-        $(".file-upload").each(function (i) {
-            var inputName = this.id;
-            var val = $("input[name='" + inputName + "']").val();
-            // 将已上传的图片路径分割成数组
-            var initialPreview = val ? val.split(',') : [];
-            $(this).fileinput({
-                uploadUrl: ctx + 'common/upload',
-                initialPreviewAsData: true,
-                initialPreview: initialPreview,
-                maxFileCount: 1,
-                allowedFileExtensions: ['jpg', 'png'],
-                maxFileSize: 10240,
-                multiple: false,
-            }).on('fileuploaded', function (event, data, previewId, index) {
-                var inputName = event.currentTarget.id;
-                var fullUrl = data.response.url; // 获取完整的URL
-                $("input[name='" + inputName + "']").val(fullUrl); // 只保留最新的图片 URL
-            }).on('fileremoved', function (event, id, index) {
-                var inputName = event.currentTarget.id;
-                $("input[name='" + inputName + "']").val(''); // 移除后清空输入框
+        // ============ 修复:初始化图片上传组件并回显已有图片 ============
+        // 获取已有的封面图片数据(逗号分隔的字符串)
+        var imgStr = [[${competition.competitionImg}]];
+        var existingImages = [];
+
+        console.log('原始图片字符串:', imgStr);
+
+        if (imgStr && imgStr.trim() !== '') {
+            // 分割逗号分隔的图片字符串
+            existingImages = imgStr.split(',').map(function(img) {
+                return img.trim();
+            }).filter(function(img) {
+                // 过滤掉空字符串和无效的URL
+                return img !== '' && img !== null && img !== undefined;
             });
 
-            $(this).fileinput('_initFileActions');
+            console.log('解析后的图片数组:', existingImages);
+            console.log('图片数量:', existingImages.length);
+        }
+
+        // 初始化图片上传组件
+        var imageUploadConfig = {
+            uploadUrl: ctx + "common/upload",
+            maxFiles: 1,
+            maxSize: 5 * 1024 * 1024,
+            allowedTypes: ['image/jpeg', 'image/png'],
+            previewWidth: 200,
+            previewHeight: 150,
+            inputName: 'competitionImg'
+        };
+
+        // 只有在有图片时才传递 initialFiles
+        if (existingImages.length > 0) {
+            imageUploadConfig.initialFiles = existingImages;
+        }
+
+        console.log('图片上传配置:', imageUploadConfig);
+
+        $('#image-upload-container').imageUpload(imageUploadConfig);
+
+        // 监听事件(可选)
+        $('#image-upload-container').on('upload.success', function(e, data) {
+            console.log('上传成功:', data.url);
+        });
+
+        $('#image-upload-container').on('upload.error', function(e, data) {
+            console.error('上传失败:', data.error);
+        });
+
+        $('#image-upload-container').on('files.set', function(e, data) {
+            console.log('图片已设置:', data.urls);
         });
-    </script>
+        // ============ 修复结束 ============
+    });
+</script>
 </body>
 </html>

+ 8 - 1
qmjszx-admin/src/main/resources/templates/include.html

@@ -108,12 +108,19 @@
 
 <!-- fileinput文件上传插件 -->
 <div th:fragment="bootstrap-fileinput-css">
-    <link th:href="@{/ajax/libs/bootstrap-fileinput/fileinput.min.css?v=5.5.2}" rel="stylesheet"/>
+    <link th:href="@{/ajax/libs/bootstrap-fileinput/fileinput.css?v=5.5.2}" rel="stylesheet"/>
 </div>
 <div th:fragment="bootstrap-fileinput-js">
     <script th:src="@{/ajax/libs/bootstrap-fileinput/fileinput.min.js?v=5.5.2}"></script>
 </div>
 
+<div th:fragment="image-upload-css">
+	<link th:href="@{/css/image-upload.css}" rel="stylesheet"/>
+</div>
+<div th:fragment="image-upload-js">
+	<script th:src="@{/js/image-upload.js}"></script>
+</div>
+
 <!-- duallistbox双列表框插件 -->
 <div th:fragment="bootstrap-duallistbox-css">
     <link th:href="@{/ajax/libs/duallistbox/bootstrap-duallistbox.min.css?v=3.0.9}" rel="stylesheet"/>

+ 6 - 1
qmjszx-admin/src/main/resources/templates/information/add.html

@@ -90,6 +90,9 @@
                     required: true, // 必填
                     digits: true, // 必须为整数
                     min: 1 // 最小值为1,确保为正整数
+                },
+                file:{
+                    required: true, // 必填
                 }
             },
             messages: {
@@ -97,6 +100,9 @@
                     required: "原价格不能为空",
                     digits: "原价格必须为整数",
                     min: "原价格必须大于0"
+                },
+                file: {
+                    required: "图片不能为空",
                 }
             }
         });
@@ -126,7 +132,6 @@
             maxFileSize: 10240,
             multiple: true,
         }).on('fileuploaded', function (event, data, previewId, index) {
-            debugger
             var inputName = event.currentTarget.id;
             var existingValue = $("input[name='" + inputName + "']").val();
             var newValue = data.response.url;

+ 7 - 1
qmjszx-admin/src/main/resources/templates/information/edit.html

@@ -66,7 +66,7 @@
             </div>
             <div class="col-xs-12">
                 <div class="form-group">
-                    <label class="col-sm-2 control-label">图片:</label>
+                    <label class="col-sm-2 control-label is-required">图片:</label>
                     <div class="col-sm-8">
                         <input type="hidden" name="file" th:field="*{file}">
                         <div class="file-loading">
@@ -91,6 +91,9 @@
                     required: true, // 必填
                     digits: true, // 必须为整数
                     min: 1 // 最小值为1,确保为正整数
+                },
+                file:{
+                    required: true, // 必填
                 }
             },
             messages: {
@@ -98,6 +101,9 @@
                     required: "原价格不能为空",
                     digits: "原价格必须为整数",
                     min: "原价格必须大于0"
+                },
+                file: {
+                    required: "图片不能为空",
                 }
             }
         });

+ 165 - 156
qmjszx-admin/src/main/resources/templates/system/notice/add.html

@@ -1,172 +1,181 @@
 <!DOCTYPE html>
-<html lang="zh" xmlns:th="http://www.thymeleaf.org" >
+<html lang="zh" xmlns:th="http://www.thymeleaf.org">
 <head>
-	<th:block th:include="include :: header('新增通知公告')" />
-	<th:block th:include="include :: summernote-css" />
-	<th:block th:include="include :: bootstrap-fileinput-css"/>
-	<!-- 自定义 CSS -->
-	<style>
-		/* 修改文本域的背景颜色和文字颜色 */
-		.note-editable {
-			background-color: black !important; /* 黑色背景 */
-			color: white !important; /* 白色文字 */
-		}
-	</style>
+    <th:block th:include="include :: header('新增通知公告')"/>
+    <th:block th:include="include :: summernote-css"/>
+    <th:block th:include="include :: image-upload-css"/>
+    <!-- 自定义 CSS -->
+    <style>
+        /* 修改文本域的背景颜色和文字颜色 */
+        .note-editable {
+            background-color: black !important; /* 黑色背景 */
+            color: white !important; /* 白色文字 */
+        }
+
+    </style>
 </head>
 <body class="white-bg">
-    <div class="wrapper wrapper-content animated fadeInRight ibox-content">
-        <form class="form-horizontal m" id="form-notice-add">
-			<div class="form-group">	
-				<label class="col-sm-2 control-label is-required">公告标题:</label>
-				<div class="col-sm-10">
-					<input id="noticeTitle" name="noticeTitle" class="form-control" type="text" required>
-				</div>
-			</div>
-			<div class="form-group">
-				<label class="col-sm-2 control-label is-required">排序:</label>
-				<div class="col-sm-10">
-					<input id="sort" name="sort" class="form-control" type="text" required>
-				</div>
-			</div>
-			<div class="form-group">
-				<label class="col-sm-2 control-label">公告类型:</label>
-				<div class="col-sm-10">
-					<select name="noticeType" class="form-control m-b" th:with="type=${@dict.getType('sys_notice_type')}">
-	                    <option th:each="dict : ${type}" th:text="${dict.dictLabel}" th:value="${dict.dictValue}"></option>
-	                </select>
-				</div>
-			</div>
-			<div class="form-group">
-				<label class="col-sm-2 control-label">置顶:</label>
-				<div class="col-sm-10">
-					<select name="topped" class="form-control m-b" th:with="type=${@dict.getType('sys_yes_no')}">
-						<option th:each="dict : ${type}" th:text="${dict.dictLabel}" th:value="${dict.dictValue}"></option>
-					</select>
-				</div>
-			</div>
-			<div class="form-group">	
-				<label class="col-sm-2 control-label">公告内容:</label>
-				<div class="col-sm-10">
-				    <input id="noticeContent" name="noticeContent" type="hidden">
-				    <div class="summernote"></div>
-				</div>
-			</div>
-			<div class="form-group">
-				<label class="col-sm-2 control-label">公告状态:</label>
-				<div class="col-sm-10">
-				    <div class="radio-box" th:each="dict : ${@dict.getType('sys_notice_status')}">
-						<input type="radio" th:id="${dict.dictCode}" name="status" th:value="${dict.dictValue}" th:checked="${dict.default}">
-						<label th:for="${dict.dictCode}" th:text="${dict.dictLabel}"></label>
-					</div>
-				</div>
-			</div>
-				<div class="form-group">
-					<label class="col-sm-2 control-label">图片:</label>
-					<div class="col-sm-10">
-						<input type="hidden" name="file">
-						<div class="file-loading">
-							<input class="form-control file-upload" id="file" name="file" type="file"  >
-						</div>
-					</div>
-				</div>
-		</form>
-	</div>
-    <th:block th:include="include :: footer" />
-    <th:block th:include="include :: summernote-js" />
-	<th:block th:include="include :: bootstrap-fileinput-js"/>
-    <script type="text/javascript">
-        var prefix = ctx + "system/notice";
+<div class="wrapper wrapper-content animated fadeInRight ibox-content">
+    <form class="form-horizontal m" id="form-notice-add">
+        <div class="form-group">
+            <label class="col-sm-2 control-label is-required">公告标题:</label>
+            <div class="col-sm-10">
+                <input id="noticeTitle" name="noticeTitle" class="form-control" type="text" required>
+            </div>
+        </div>
+        <div class="form-group">
+            <label class="col-sm-2 control-label is-required">排序:</label>
+            <div class="col-sm-10">
+                <input id="sort" name="sort" class="form-control" type="text" required>
+            </div>
+        </div>
+        <div class="form-group">
+            <label class="col-sm-2 control-label">公告类型:</label>
+            <div class="col-sm-10">
+                <select name="noticeType" class="form-control m-b" th:with="type=${@dict.getType('sys_notice_type')}">
+                    <option th:each="dict : ${type}" th:text="${dict.dictLabel}" th:value="${dict.dictValue}"></option>
+                </select>
+            </div>
+        </div>
+        <div class="form-group">
+            <label class="col-sm-2 control-label">置顶:</label>
+            <div class="col-sm-10">
+                <select name="topped" class="form-control m-b" th:with="type=${@dict.getType('sys_yes_no')}">
+                    <option th:each="dict : ${type}" th:text="${dict.dictLabel}" th:value="${dict.dictValue}"></option>
+                </select>
+            </div>
+        </div>
+        <div class="form-group">
+            <label class="col-sm-2 control-label">公告内容:</label>
+            <div class="col-sm-10">
+                <input id="noticeContent" name="noticeContent" type="hidden">
+                <div class="summernote"></div>
+            </div>
+        </div>
+        <div class="form-group">
+            <label class="col-sm-2 control-label">公告状态:</label>
+            <div class="col-sm-10">
+                <div class="radio-box" th:each="dict : ${@dict.getType('sys_notice_status')}">
+                    <input type="radio" th:id="${dict.dictCode}" name="status" th:value="${dict.dictValue}"
+                           th:checked="${dict.default}">
+                    <label th:for="${dict.dictCode}" th:text="${dict.dictLabel}"></label>
+                </div>
+            </div>
+        </div>
+        <div class="form-group">
+            <label class="col-sm-2 control-label">图片:</label>
+            <div class="col-sm-10">
+                <div id="image-upload-container"></div>
+            </div>
+        </div>
+    </form>
+</div>
+<th:block th:include="include :: footer"/>
+<th:block th:include="include :: summernote-js"/>
+<th:block th:include="include :: image-upload-js"/>
+<script type="text/javascript">
+    var prefix = ctx + "system/notice";
+
+    $('.summernote').summernote({
+        placeholder: '请输入公告内容',
+        height: 192,
+        lang: 'zh-CN',
+        followingToolbar: false,
+        dialogsInBody: true,
+        callbacks: {
+            onImageUpload: function (files) {
+                sendFile(files[0], this);
+            }
+        }
+    });
+
+    // 初始化图片上传组件
+    $('#image-upload-container').imageUpload({
+        uploadUrl: ctx + "common/upload",
+        maxFiles: 2,
+        maxSize: 5 * 1024 * 1024,
+        allowedTypes: ['image/jpeg', 'image/png'],
+        previewWidth: 200,
+        previewHeight: 150,
+        inputName: 'noticeImg'
+    });
 
-	    $('.summernote').summernote({
-	    	placeholder: '请输入公告内容',
-			height : 192,
-			lang : 'zh-CN',
-			followingToolbar: false,
-			dialogsInBody: true,
-			callbacks: {
-                onImageUpload: function (files) {
-                    sendFile(files[0], this);
+    // 上传文件
+    function sendFile(file, obj) {
+        var data = new FormData();
+        data.append("file", file);
+        debugger
+        $.ajax({
+            type: "POST",
+            url: ctx + "common/upload",
+            data: data,
+            cache: false,
+            contentType: false,
+            processData: false,
+            dataType: 'json',
+            success: function (result) {
+                if (result.code == web_status.SUCCESS) {
+                    $(obj).summernote('editor.insertImage', result.url, result.fileName);
+                } else {
+                    $.modal.alertError(result.msg);
                 }
+            },
+            error: function (error) {
+                $.modal.alertWarning("图片上传失败。");
             }
-		});
+        });
+    }
 
+    $("#form-notice-add").validate({
+        focusCleanup: true,
+        rules: {
+            sort: {
+                digits: true
+            },
+        },
+    });
 
-		$(".file-upload").each(function (i) {
-			var inputName = this.id;
-			var val = $("input[name='" + inputName + "']").val();
-			// 将已上传的图片路径分割成数组
-			var initialPreview = val ? val.split(',') : [];
+    function submitHandler() {
+        var uploadInstance = $('#image-upload-container').data('imageUpload');
 
-			$(this).fileinput({
-				uploadUrl: ctx + 'common/upload',
-				initialPreviewAsData: true,
-				initialPreview: initialPreview,
-				maxFileCount: 1,
-				allowedFileExtensions: ['jpg', 'png'],
-				maxFileSize: 10240,
-				multiple: false,
-			}).on('fileuploaded', function (event, data, previewId, index) {
-				var inputName = event.currentTarget.id;
-				var existingValue = $("input[name='" + inputName + "']").val();
-				var newValue = data.response.url;
+        // 检查是否有上传组件实例
+        if (uploadInstance) {
+            var uploadedFiles = uploadInstance.getFiles();
+            console.log('已上传的图片:', uploadedFiles);
 
-				// 如果已经存在值,则在后面加上逗号分隔的新值
-				if (existingValue) {
-					$("input[name='" + inputName + "']").val(existingValue + ',' + newValue);
-				} else {
-					$("input[name='" + inputName + "']").val(newValue);
-				}
-			}).on('fileremoved', function (event, key, jqXHR, pd) {
-				var inputName = event.currentTarget.id;
-				// 移除文件后,清空输入框的值
-				$("input[name='" + inputName + "']").val('');
-			});
+            // 如果需要封面必填
+            if (uploadedFiles.length === 0) {
+                $.modal.alertWarning("请上传封面图片");
+                return false;
+            }
+
+            // 将图片URL数组转换为逗号分隔的字符串
+            var imageUrls = uploadedFiles.join(',');
+            console.log('图片URL字符串:', imageUrls);
 
-			$(this).fileinput('_initFileActions');
-		});
+            // 注意:图片上传组件已经通过 addHiddenField 方法添加了隐藏字段
+            // 我们不需要再次添加,只需要确保所有图片都已被收集
+            // 组件会自动处理隐藏字段
+        } else {
+            // 如果没有上传组件实例,直接检查是否有隐藏字段
+            var existingImages = $('#form-competition-add input[name="competitionImg"]').map(function() {
+                return $(this).val();
+            }).get().filter(function(val) {
+                return val && val.trim() !== '';
+            });
 
-	    // 上传文件
-	    function sendFile(file, obj) {
-	        var data = new FormData();
-	        data.append("file", file);
-	        $.ajax({
-	            type: "POST",
-	            url: ctx + "common/upload",
-	            data: data,
-	            cache: false,
-	            contentType: false,
-	            processData: false,
-	            dataType: 'json',
-	            success: function(result) {
-	                if (result.code == web_status.SUCCESS) {
-	                	$(obj).summernote('editor.insertImage', result.url, result.fileName);
-					} else {
-						$.modal.alertError(result.msg);
-					}
-	            },
-	            error: function(error) {
-	                $.modal.alertWarning("图片上传失败。");
-	            }
-	        });
-	    }
-		
-		$("#form-notice-add").validate({
-			focusCleanup: true,
-			rules:{
-				sort:{
-					digits:true
-				},
-			},
-		});
-		
-		function submitHandler() {
-	        if ($.validate.form()) {
-	        	var sHTML = $('.summernote').summernote('code');
-				$("#noticeContent").val(sHTML);
-				$.operate.save(prefix + "/add", $('#form-notice-add').serialize());
-	        }
-	    }
+            if (existingImages.length === 0) {
+                $.modal.alertWarning("请上传封面图片");
+                return false;
+            }
+        }
+        if ($.validate.form()) {
+            var sHTML = $('.summernote').summernote('code');
+            $("#noticeContent").val(sHTML);
+            $.operate.save(prefix + "/add", $('#form-notice-add').serialize());
+        }
+    }
 
-	</script>
+</script>
 </body>
 </html>

+ 258 - 174
qmjszx-admin/src/main/resources/templates/system/notice/edit.html

@@ -1,9 +1,9 @@
 <!DOCTYPE html>
-<html lang="zh" xmlns:th="http://www.thymeleaf.org" >
+<html lang="zh" xmlns:th="http://www.thymeleaf.org">
 <head>
-	<th:block th:include="include :: header('修改通知公告')" />
-	<th:block th:include="include :: summernote-css" />
-    <th:block th:include="include :: bootstrap-fileinput-css"/>
+    <th:block th:include="include :: header('修改通知公告')"/>
+    <th:block th:include="include :: summernote-css"/>
+    <th:block th:include="include :: image-upload-css"/>
     <!-- 自定义 CSS -->
     <style>
         /* 修改文本域的背景颜色和文字颜色 */
@@ -14,187 +14,271 @@
     </style>
 </head>
 <body class="white-bg">
-    <div class="wrapper wrapper-content animated fadeInRight ibox-content">
-        <form class="form-horizontal m" id="form-notice-edit" th:object="${notice}">
-            <input id="noticeId" name="noticeId" th:field="*{noticeId}"  type="hidden">
-            <div class="form-group">	
-                <label class="col-sm-2 control-label is-required">公告标题:</label>
-                <div class="col-sm-10">
-                    <input id="noticeTitle" name="noticeTitle" th:field="*{noticeTitle}" class="form-control" type="text" required>
-                </div>
+<div class="wrapper wrapper-content animated fadeInRight ibox-content">
+    <form class="form-horizontal m" id="form-notice-edit" th:object="${notice}">
+        <input id="noticeId" name="noticeId" th:field="*{noticeId}" type="hidden">
+        <div class="form-group">
+            <label class="col-sm-2 control-label is-required">公告标题:</label>
+            <div class="col-sm-10">
+                <input id="noticeTitle" name="noticeTitle" th:field="*{noticeTitle}" class="form-control" type="text"
+                       required>
             </div>
-            <div class="form-group">
-                <label class="col-sm-2 control-label is-required">排序:</label>
-                <div class="col-sm-10">
-                    <input id="sort" name="sort" th:field="*{sort}" class="form-control" type="text" required>
-                </div>
+        </div>
+        <div class="form-group">
+            <label class="col-sm-2 control-label is-required">排序:</label>
+            <div class="col-sm-10">
+                <input id="sort" name="sort" th:field="*{sort}" class="form-control" type="text" required>
             </div>
-            <div class="form-group">
-				<label class="col-sm-2 control-label">公告类型:</label>
-				<div class="col-sm-10">
-					<select name="noticeType" class="form-control m-b" th:with="type=${@dict.getType('sys_notice_type')}">
-	                    <option th:each="dict : ${type}" th:text="${dict.dictLabel}" th:value="${dict.dictValue}" th:field="*{noticeType}"></option>
-	                </select>
-				</div>
-			</div>
-            <div class="form-group">
-                <label class="col-sm-2 control-label">置顶:</label>
-                <div class="col-sm-10">
-                    <select name="topped" class="form-control m-b" th:with="type=${@dict.getType('sys_yes_no')}">
-                        <option th:each="dict : ${type}" th:text="${dict.dictLabel}" th:value="${dict.dictValue}" th:field="*{topped}"></option>
-                    </select>
-                </div>
+        </div>
+        <div class="form-group">
+            <label class="col-sm-2 control-label">公告类型:</label>
+            <div class="col-sm-10">
+                <select name="noticeType" class="form-control m-b" th:with="type=${@dict.getType('sys_notice_type')}">
+                    <option th:each="dict : ${type}" th:text="${dict.dictLabel}" th:value="${dict.dictValue}"
+                            th:field="*{noticeType}"></option>
+                </select>
             </div>
-            <div class="form-group">	
-                <label class="col-sm-2 control-label">公告内容:</label>
-                <div class="col-sm-10">
-                    <input id="noticeContent" name="noticeContent" th:field="*{noticeContent}" type="hidden">
-                    <div id="editor" class="summernote"></div>
-                </div>
+        </div>
+        <div class="form-group">
+            <label class="col-sm-2 control-label">置顶:</label>
+            <div class="col-sm-10">
+                <select name="topped" class="form-control m-b" th:with="type=${@dict.getType('sys_yes_no')}">
+                    <option th:each="dict : ${type}" th:text="${dict.dictLabel}" th:value="${dict.dictValue}"
+                            th:field="*{topped}"></option>
+                </select>
+            </div>
+        </div>
+        <div class="form-group">
+            <label class="col-sm-2 control-label">公告内容:</label>
+            <div class="col-sm-10">
+                <input id="noticeContent" name="noticeContent" th:field="*{noticeContent}" type="hidden">
+                <div id="editor" class="summernote"></div>
             </div>
-            <div class="form-group">
-				<label class="col-sm-2 control-label">公告状态:</label>
-				<div class="col-sm-10">
-					<div class="radio-box" th:each="dict : ${@dict.getType('sys_notice_status')}">
-						<input type="radio" th:id="${dict.dictCode}" name="status" th:value="${dict.dictValue}" th:field="*{status}">
-						<label th:for="${dict.dictCode}" th:text="${dict.dictLabel}"></label>
-					</div>
-				</div>
-			</div>
-                <div class="form-group">
-                    <label class="col-sm-2 control-label">图片:</label>
-                    <div class="col-sm-10">
-                        <input type="hidden" name="noticeImg" th:field="*{noticeImg}">
-                        <input type="hidden" name="file" th:field="*{file}">
-                        <div class="file-loading">
-                            <input class="form-control file-upload" id="file" name="file" type="file" >
-                        </div>
-                    </div>
+        </div>
+        <div class="form-group">
+            <label class="col-sm-2 control-label">公告状态:</label>
+            <div class="col-sm-10">
+                <div class="radio-box" th:each="dict : ${@dict.getType('sys_notice_status')}">
+                    <input type="radio" th:id="${dict.dictCode}" name="status" th:value="${dict.dictValue}"
+                           th:field="*{status}">
+                    <label th:for="${dict.dictCode}" th:text="${dict.dictLabel}"></label>
                 </div>
-		</form>
-    </div>
-    <th:block th:include="include :: footer" />
-    <th:block th:include="include :: summernote-js" />
-    <th:block th:include="include :: bootstrap-fileinput-js"/>
-    <script type="text/javascript">
-        var prefix = ctx + "system/notice";
-    
-	    $(function() {
-		    $('.summernote').summernote({
-		    	placeholder: '请输入公告内容',
-		    	height : 192,
-				lang : 'zh-CN',
-				followingToolbar: false,
-				dialogsInBody: true,
-				callbacks: {
-                    onChange: function(contents, $edittable) {
-                        $("input[name='" + this.id + "']").val(contents);
-                    },
-                    onImageUpload: function(files) {
-                        var obj = this;
-                        var data = new FormData();
-                        data.append("file", files[0]);
-                        $.ajax({
-                            type: "post",
-                            url: ctx + "common/upload",
-                            data: data,
-                            cache: false,
-                            contentType: false,
-                            processData: false,
-                            dataType: 'json',
-                            success: function(result) {
-                                if (result.code == web_status.SUCCESS) {
-                                    $('#' + obj.id).summernote('editor.insertImage', result.url);
-                                } else {
-                                    $.modal.alertError(result.msg);
-                                }
-                            },
-                            error: function(error) {
-                                $.modal.alertWarning("图片上传失败。");
+            </div>
+        </div>
+        <div class="form-group">
+            <label class="col-sm-2 control-label">图片:</label>
+            <div class="col-sm-10">
+                <div id="image-upload-container"></div>
+            </div>
+        </div>
+    </form>
+</div>
+<th:block th:include="include :: footer"/>
+<th:block th:include="include :: summernote-js"/>
+<th:block th:include="include :: image-upload-js"/>
+<script type="text/javascript">
+    var prefix = ctx + "system/notice";
+
+    $(function () {
+        $('.summernote').summernote({
+            placeholder: '请输入公告内容',
+            height: 192,
+            lang: 'zh-CN',
+            followingToolbar: false,
+            dialogsInBody: true,
+            callbacks: {
+                onChange: function (contents, $edittable) {
+                    $("input[name='" + this.id + "']").val(contents);
+                },
+                onImageUpload: function (files) {
+                    var obj = this;
+                    var data = new FormData();
+                    data.append("file", files[0]);
+                    $.ajax({
+                        type: "post",
+                        url: ctx + "common/upload",
+                        data: data,
+                        cache: false,
+                        contentType: false,
+                        processData: false,
+                        dataType: 'json',
+                        success: function (result) {
+                            if (result.code == web_status.SUCCESS) {
+                                $('#' + obj.id).summernote('editor.insertImage', result.url);
+                            } else {
+                                $.modal.alertError(result.msg);
                             }
-                        });
-                    }
-	            }
-		    });
-			var content = $("#noticeContent").val();
-		    $('#editor').summernote('code', content);
-	    });
-
-
-        $(".file-upload").each(function (i) {
-            var inputName = this.id;
-            var val = $("input[name='" + inputName + "']").val();
-            // 将已上传的图片路径分割成数组
-            var initialPreview = val ? val.split(',') : [];
-            $(this).fileinput({
-                uploadUrl: ctx + 'common/upload',
-                initialPreviewAsData: true,
-                initialPreview: initialPreview,
-                maxFileCount: 1,
-                allowedFileExtensions: ['jpg', 'png'],
-                maxFileSize: 10240,
-                overwriteInitial:true,
-                multiple: false,
-            }).on('fileuploaded', function (event, data, previewId, index) {
-                var inputName = event.currentTarget.id;
-                var existingValue = $("input[name='" + inputName + "']").val();
-                var newValue = data.response.url;
-
-                // 如果已经存在值,则在后面加上逗号分隔的新值
-                if (existingValue) {
-                    $("input[name='" + inputName + "']").val(existingValue + ',' + newValue);
-                } else {
-                    $("input[name='" + inputName + "']").val(newValue);
+                        },
+                        error: function (error) {
+                            $.modal.alertWarning("图片上传失败。");
+                        }
+                    });
                 }
-            }).on('fileremoved', function (event, id, index) {
-                var inputName = event.currentTarget.id;
-                $("input[name='" + inputName + "']").val(''); // 移除后清空输入框
+            }
+        });
+        var content = $("#noticeContent").val();
+        $('#editor').summernote('code', content);
+
+        // ============ 修复:初始化图片上传组件并回显已有图片 ============
+        console.log(typeof "${notice.noticeImg}")
+        // 获取已有的封面图片数据(逗号分隔的字符串)
+        var imgStr = [[${notice.noticeImg}]];
+        var existingImages = [];
+
+        console.log('原始图片字符串:', imgStr);
+
+        if (imgStr && imgStr.trim() !== '') {
+            // 分割逗号分隔的图片字符串
+            existingImages = imgStr.split(',').map(function(img) {
+                return img.trim();
+            }).filter(function(img) {
+                // 过滤掉空字符串和无效的URL
+                return img !== '' && img !== null && img !== undefined;
             });
 
-            $(this).fileinput('_initFileActions');
+            console.log('解析后的图片数组:', existingImages);
+            console.log('图片数量:', existingImages.length);
+        }
+
+        // 初始化图片上传组件
+        var imageUploadConfig = {
+            uploadUrl: ctx + "common/upload",
+            maxFiles: 1,
+            maxSize: 5 * 1024 * 1024,
+            allowedTypes: ['image/jpeg', 'image/png'],
+            previewWidth: 200,
+            previewHeight: 150,
+            inputName: 'noticeImg'
+        };
+
+        // 只有在有图片时才传递 initialFiles
+        if (existingImages.length > 0) {
+            imageUploadConfig.initialFiles = existingImages;
+        }
+
+        console.log('图片上传配置:', imageUploadConfig);
+
+        $('#image-upload-container').imageUpload(imageUploadConfig);
+
+        // 监听事件(可选)
+        $('#image-upload-container').on('upload.success', function(e, data) {
+            console.log('上传成功:', data.url);
         });
 
-	    // 上传文件
-	    function sendFile(file, obj) {
-	        var data = new FormData();
-	        data.append("file", file);
-	        $.ajax({
-	            type: "POST",
-	            url: ctx + "common/upload",
-	            data: data,
-	            cache: false,
-	            contentType: false,
-	            processData: false,
-	            dataType: 'json',
-	            success: function(result) {
-	                if (result.code == web_status.SUCCESS) {
-	                	$(obj).summernote('editor.insertImage', result.url, result.fileName);
-					} else {
-						$.modal.alertError(result.msg);
-					}
-	            },
-	            error: function(error) {
-	                $.modal.alertWarning("图片上传失败。");
-	            }
-	        });
-	    }
-	    
-		$("#form-notice-edit").validate({
-			focusCleanup: true,
-            rules:{
-                sort:{
-                    digits:true
-                },
+        $('#image-upload-container').on('upload.error', function(e, data) {
+            console.error('上传失败:', data.error);
+        });
+
+        $('#image-upload-container').on('files.set', function(e, data) {
+            console.log('图片已设置:', data.urls);
+        });
+        // ============ 修复结束 ============
+    });
+
+
+    $(".file-upload").each(function (i) {
+        var inputName = this.id;
+        var val = $("input[name='" + inputName + "']").val();
+        // 将已上传的图片路径分割成数组
+        var initialPreview = val ? val.split(',') : [];
+        $(this).fileinput({
+            uploadUrl: ctx + 'common/upload',
+            initialPreviewAsData: true,
+            initialPreview: initialPreview,
+            maxFileCount: 1,
+            allowedFileExtensions: ['jpg', 'png'],
+            maxFileSize: 10240,
+            overwriteInitial: true,
+            multiple: false,
+        }).on('fileuploaded', function (event, data, previewId, index) {
+            var inputName = event.currentTarget.id;
+            var existingValue = $("input[name='" + inputName + "']").val();
+            var newValue = data.response.url;
+
+            // 如果已经存在值,则在后面加上逗号分隔的新值
+            if (existingValue) {
+                $("input[name='" + inputName + "']").val(existingValue + ',' + newValue);
+            } else {
+                $("input[name='" + inputName + "']").val(newValue);
+            }
+        }).on('fileremoved', function (event, id, index) {
+            var inputName = event.currentTarget.id;
+            $("input[name='" + inputName + "']").val(''); // 移除后清空输入框
+        });
+
+        $(this).fileinput('_initFileActions');
+    });
+
+    // 上传文件
+    function sendFile(file, obj) {
+        var data = new FormData();
+        data.append("file", file);
+        $.ajax({
+            type: "POST",
+            url: ctx + "common/upload",
+            data: data,
+            cache: false,
+            contentType: false,
+            processData: false,
+            dataType: 'json',
+            success: function (result) {
+                if (result.code == web_status.SUCCESS) {
+                    $(obj).summernote('editor.insertImage', result.url, result.fileName);
+                } else {
+                    $.modal.alertError(result.msg);
+                }
+            },
+            error: function (error) {
+                $.modal.alertWarning("图片上传失败。");
+            }
+        });
+    }
+
+    $("#form-notice-edit").validate({
+        focusCleanup: true,
+        rules: {
+            sort: {
+                digits: true
             },
-		});
-		
-		function submitHandler() {
-	        if ($.validate.form()) {
-	        	var sHTML = $('.summernote').summernote('code');
-				$("#noticeContent").val(sHTML);
-				$.operate.save(prefix + "/edit", $('#form-notice-edit').serialize());
-	        }
-	    }
-	</script>
+        },
+    });
+
+    function submitHandler() {
+        if ($.validate.form()) {
+            // 获取上传组件实例
+            var uploadInstance = $('#image-upload-container').data('imageUpload');
+
+            // 检查是否有上传的图片
+            if (uploadInstance) {
+                var uploadedFiles = uploadInstance.getFiles();
+                console.log('已上传的图片:', uploadedFiles);
+
+                // 检查封面图片是否上传
+                if (uploadedFiles.length === 0) {
+                    $.modal.alertWarning("请上传封面图片");
+                    return false;
+                }
+                // 组件会自动更新隐藏字段,我们不需要额外处理
+            } else {
+                // 如果没有上传组件实例,检查是否有隐藏字段
+                var existingImages = $('#form-competition-edit input[name="noticeImg"]').map(function() {
+                    return $(this).val();
+                }).get().filter(function(val) {
+                    return val && val.trim() !== '';
+                });
+
+                if (existingImages.length === 0) {
+                    $.modal.alertWarning("请上传封面图片");
+                    return false;
+                }
+            }
+
+
+            var sHTML = $('.summernote').summernote('code');
+            $("#noticeContent").val(sHTML);
+            $.operate.save(prefix + "/edit", $('#form-notice-edit').serialize());
+        }
+    }
+</script>
 </body>
 </html>

+ 5 - 0
qmjszx-business/src/main/java/beilv/competition/domain/Competition.java

@@ -11,6 +11,8 @@ import org.apache.commons.lang3.builder.ToStringStyle;
 import beilv.common.annotation.Excel;
 import beilv.common.core.domain.BaseEntity;
 
+import javax.validation.constraints.NotBlank;
+
 /**
  * 赛事发布对象 competition
  *
@@ -145,6 +147,9 @@ public class Competition extends BaseEntity {
      */
     private String competitionImg;
 
+    @NotBlank(message = "封面不能为空")
+    private String file;
+
     private Long allowPublishTime;
 
     //参赛费用

+ 0 - 1
qmjszx-system/src/main/java/beilv/system/domain/SysNotice.java

@@ -47,7 +47,6 @@ public class SysNotice extends BaseEntity
 
     /** 图片 */
     private String noticeImg;
-    private String file;
 
     public Long getNoticeId()
     {

+ 1 - 16
qmjszx-system/src/main/java/beilv/system/service/impl/SysNoticeServiceImpl.java

@@ -40,11 +40,7 @@ public class SysNoticeServiceImpl implements ISysNoticeService {
      */
     @Override
     public List<SysNotice> selectNoticeList(SysNotice notice) {
-        List<SysNotice> sysNotices = noticeMapper.selectNoticeList(notice);
-        sysNotices.forEach(sysNotice -> {
-            sysNotice.setFile(sysNotice.getNoticeImg());
-        });
-        return sysNotices;
+        return noticeMapper.selectNoticeList(notice);
     }
 
     /**
@@ -56,7 +52,6 @@ public class SysNoticeServiceImpl implements ISysNoticeService {
     @Override
     public int insertNotice(SysNotice notice) {
         notice.setReleaseTime(DateUtils.getNowDate());
-        notice.setNoticeImg(notice.getFile());
         return noticeMapper.insertNotice(notice);
     }
 
@@ -69,16 +64,6 @@ public class SysNoticeServiceImpl implements ISysNoticeService {
     @Override
     public int updateNotice(SysNotice notice) {
         notice.setReleaseTime(DateUtils.getNowDate());
-        notice.setNoticeImg(notice.getFile());
-//        List<SysNotice> top = noticeMapper.getTopNotice(notice);
-//        if (top != null && !top.isEmpty() && notice.getTopped().equals("Y")) {
-//            // 已有置顶记录,且新的置顶状态为 "Y" 但不是同一个记录
-//            boolean isSameNotice = top.stream()
-//                    .anyMatch(existingNotice -> existingNotice.getNoticeId().equals(notice.getNoticeId()));
-//            if (!isSameNotice) {
-//                throw new DataIntegrityViolationException("已有置顶公告,请修改状态再进行设置");
-//            }
-//        }
         return noticeMapper.updateNotice(notice);
     }