510 行
24 KiB
JavaScript
510 行
24 KiB
JavaScript
define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'backend/format'], function ($, undefined, Backend, Table, Form, Format) {
|
||
|
||
/**
|
||
* categories 目录下的专属图标键名列表
|
||
*/
|
||
var CATEGORY_DIR_KEYS = [
|
||
'cat-fruit', 'cat-poultry', 'cat-veg', 'cat-job',
|
||
'fruit-apple', 'fruit-grape', 'fruit-kiwi', 'fruit-orange', 'fruit-passion', 'fruit-peach', 'fruit-strawberry', 'fruit-watermelon',
|
||
'veg-cabbage', 'veg-chili', 'veg-green-cabbage', 'veg-greens', 'veg-purple-cabbage', 'veg-radish', 'veg-tomato'
|
||
];
|
||
|
||
/**
|
||
* 解析分类图标/实拍图地址
|
||
* @param {string} iconKey 标识或路径
|
||
* @param {number} id 分类ID
|
||
* @param {string} image 实拍图路径(可选)
|
||
* @returns {string} 完整的静态图片URL
|
||
*/
|
||
function resolveIconUrl(iconKey, id, image) {
|
||
if (image && /^(https?:\/\/|\/uploads\/|\/static\/)/i.test(image)) {
|
||
return image;
|
||
}
|
||
if (!iconKey) {
|
||
iconKey = 'cat-job';
|
||
}
|
||
if (/^(https?:\/\/|\/uploads\/|\/static\/)/i.test(iconKey)) {
|
||
return iconKey;
|
||
}
|
||
var baseKey = iconKey.replace(/\.(png|jpg|jpeg|gif|svg)$/i, '');
|
||
var file = (iconKey.indexOf('.') !== -1) ? iconKey : iconKey + '.png';
|
||
if (iconKey.indexOf('sub-') === 0 || CATEGORY_DIR_KEYS.indexOf(baseKey) !== -1 || /^(veg-|fruit-|cat-veg|cat-fruit|cat-job|cat-poultry)/i.test(baseKey)) {
|
||
return '/static/categories/' + file + '?v=2';
|
||
}
|
||
return '/static/icons/' + file + '?v=2';
|
||
}
|
||
|
||
var Controller = {
|
||
index: function () {
|
||
// 初始化表格参数配置
|
||
Table.api.init({
|
||
extend: {
|
||
index_url: 'fc/category/index' + location.search,
|
||
add_url: 'fc/category/add',
|
||
edit_url: 'fc/category/edit',
|
||
del_url: 'fc/category/del',
|
||
multi_url: 'fc/category/multi',
|
||
import_url: 'fc/category/import',
|
||
table: 'fc_category',
|
||
}
|
||
});
|
||
|
||
var table = $("#table");
|
||
|
||
// 初始化表格
|
||
table.bootstrapTable({
|
||
url: $.fn.bootstrapTable.defaults.extend.index_url,
|
||
pk: 'id',
|
||
sortName: 'sort',
|
||
sortOrder: 'asc',
|
||
escape: false,
|
||
columns: [
|
||
[
|
||
{checkbox: true, width: '40px'},
|
||
{
|
||
field: 'id',
|
||
title: __('SerialNo'),
|
||
operate: false,
|
||
sortable: false,
|
||
align: 'center',
|
||
width: '60px',
|
||
class: 'fc-serial',
|
||
table: table,
|
||
formatter: Format.serial
|
||
},
|
||
|
||
// 1. 图标/实拍图预览(大图圆角卡,object-fit:cover)
|
||
{
|
||
field: 'image',
|
||
title: '图标/图片',
|
||
operate: false,
|
||
align: 'center',
|
||
width: '90px',
|
||
formatter: function (value, row, index) {
|
||
var url = resolveIconUrl(row.icon_key, row.id, row.image);
|
||
var color = row.color || '#00E070';
|
||
return '<div class="fc-img-wrap">' +
|
||
'<div style="display:inline-block;width:56px;height:56px;border-radius:14px;overflow:hidden;background:' + color + '15;border:1px solid ' + color + '40;box-shadow:0 2px 8px rgba(4,48,30,0.08);cursor:zoom-in;">' +
|
||
'<img src="' + url + '" style="width:100%;height:100%;object-fit:cover;display:block;" onerror="this.src=\'/static/categories/cat-job.png\'" />' +
|
||
'</div>' +
|
||
'<div class="fc-img-zoom"><img src="' + url + '" onerror="this.src=\'/static/categories/cat-job.png\'" /></div>' +
|
||
'</div>';
|
||
}
|
||
},
|
||
|
||
// 2. 分类名称(含层级缩进,带主题色小圆点)
|
||
{
|
||
field: 'name',
|
||
title: __('Name'),
|
||
operate: 'LIKE',
|
||
align: 'left',
|
||
width: '200px',
|
||
formatter: function (value, row, index) {
|
||
var color = row.color || '#00E070';
|
||
var isSub = Number(row.pid || 0) > 0;
|
||
var dot = '<span style="flex:0 0 auto;display:inline-block;width:10px;height:10px;border-radius:50%;background:' + color + ';margin-right:10px;vertical-align:middle;box-shadow:0 0 6px ' + color + '99;"></span>';
|
||
var nameStyle = isSub
|
||
? 'font-weight:500;font-size:13.5px;color:#475569;'
|
||
: 'font-weight:700;font-size:14px;color:#1e293b;';
|
||
return '<div class="' + (isSub ? 'fc-tree-sub' : '') + '" style="display:flex;align-items:center;">' + dot + '<span style="' + nameStyle + '">' + value + '</span></div>';
|
||
}
|
||
},
|
||
|
||
// 2.5 所属大类(关联实际 pid 字段,支持普通搜索按大类下拉过滤)
|
||
{
|
||
field: 'pid',
|
||
title: '所属分类',
|
||
align: 'center',
|
||
width: '130px',
|
||
searchList: (Config && Config.parentList) ? Config.parentList : {},
|
||
formatter: function (value, row, index) {
|
||
if (!row.pid || Number(row.pid) === 0) {
|
||
return '<span class="label" style="background:#ecfdf5;color:#059669;border:1px solid #a7f3d0;border-radius:6px;padding:3px 8px;font-weight:600;">一级大类</span>';
|
||
}
|
||
return '<span class="label" style="background:#f1f5f9;color:#475569;border-radius:6px;padding:3px 8px;">' + (row.parent_name || ('#' + row.pid)) + '</span>';
|
||
}
|
||
},
|
||
|
||
// 2.6 分类类型(决定发布页是否显示产地规格字段,支持下拉筛选)
|
||
{
|
||
field: 'kind',
|
||
title: '类型',
|
||
align: 'center',
|
||
width: '100px',
|
||
searchList: {'farm': '农产品', 'general': '通用信息'},
|
||
formatter: function (value, row, index) {
|
||
if (value === 'farm') {
|
||
return '<span class="label" style="background:#ecfdf5;color:#059669;border:1px solid #a7f3d0;border-radius:6px;padding:3px 8px;font-weight:600;">农产品</span>';
|
||
}
|
||
return '<span class="label" style="background:#eff6ff;color:#2563eb;border:1px solid #bfdbfe;border-radius:6px;padding:3px 8px;">通用信息</span>';
|
||
}
|
||
},
|
||
|
||
// 4. 排序权重
|
||
{
|
||
field: 'sort',
|
||
title: __('Sort'),
|
||
align: 'center',
|
||
width: '110px',
|
||
sortable: true,
|
||
formatter: function (value, row, index) {
|
||
return '<span class="badge" style="background:#eef7f2;color:#04874a;font-weight:700;font-size:13px;padding:4px 10px;border-radius:8px;border:1px solid #bcead1;">' + value + '</span>';
|
||
}
|
||
},
|
||
|
||
// 5. 发布帖子数
|
||
{
|
||
field: 'post_count',
|
||
title: __('Post_count'),
|
||
align: 'center',
|
||
width: '120px',
|
||
sortable: true,
|
||
formatter: function (value, row, index) {
|
||
var count = Number(value || 0);
|
||
var bg = count > 0 ? '#f0fdf4' : '#f8f9fa';
|
||
var text = count > 0 ? '#15803d' : '#888';
|
||
return '<span class="label" style="background:' + bg + ';color:' + text + ';border-radius:8px;padding:4px 10px;font-size:12px;font-weight:600;display:inline-block;">' + count + ' 篇</span>';
|
||
}
|
||
},
|
||
|
||
// 6. 启用状态(原生点击切换开关)
|
||
{
|
||
field: 'enabled',
|
||
title: __('Enabled'),
|
||
align: 'center',
|
||
width: '100px',
|
||
searchList: {0: '已禁用', 1: '已启用'},
|
||
formatter: Table.api.formatter.toggle
|
||
},
|
||
|
||
// 6.5 首页金刚区推荐(原生点击切换开关,最多8个二级品类)
|
||
{
|
||
field: 'is_home',
|
||
title: '首页金刚区',
|
||
align: 'center',
|
||
width: '120px',
|
||
searchList: {0: '未推荐', 1: '已推荐'},
|
||
formatter: function (value, row, index) {
|
||
if (!row.pid || Number(row.pid) === 0) {
|
||
return '<span class="text-muted" style="color:#94a3b8;font-size:12px;">— 一级不展示 —</span>';
|
||
}
|
||
return Table.api.formatter.toggle.call(this, value, row, index);
|
||
}
|
||
},
|
||
|
||
// 7. 更新时间
|
||
{
|
||
field: 'updatetime',
|
||
title: __('Updatetime'),
|
||
operate: 'RANGE',
|
||
addclass: 'datetimerange',
|
||
autocomplete: false,
|
||
align: 'center',
|
||
width: '180px',
|
||
formatter: Table.api.formatter.datetime
|
||
},
|
||
|
||
// 8. 操作栏
|
||
{
|
||
field: 'operate',
|
||
title: __('Operate'),
|
||
table: table,
|
||
align: 'center',
|
||
width: '120px',
|
||
events: Table.api.events.operate,
|
||
formatter: Table.api.formatter.operate
|
||
}
|
||
]
|
||
]
|
||
});
|
||
|
||
// 为表格绑定事件
|
||
Table.api.bindevent(table);
|
||
|
||
/**
|
||
* 快捷层级与状态筛选(全部/一级大类/二级品类/首页金刚区推荐)
|
||
* @param {Object} filterObj 过滤参数键值对
|
||
* @param {Object} opObj 操作符参数键值对
|
||
* @param {HTMLElement} activeBtn 触发按钮
|
||
*/
|
||
function applyQuickFilter(filterObj, opObj, activeBtn) {
|
||
$('.fc-quick-filters .btn').removeClass('active');
|
||
if (activeBtn) {
|
||
$(activeBtn).addClass('active');
|
||
}
|
||
var options = table.bootstrapTable('getOptions');
|
||
var queryParams = options.queryParams;
|
||
options.pageNumber = 1;
|
||
options.queryParams = function (params) {
|
||
var p = typeof queryParams === 'function' ? queryParams(params) : params;
|
||
var filter = p.filter ? JSON.parse(p.filter) : {};
|
||
var op = p.op ? JSON.parse(p.op) : {};
|
||
|
||
// 清理快捷筛选旧字段,避免状态叠加冲突
|
||
delete filter.pid;
|
||
delete op.pid;
|
||
delete filter.is_home;
|
||
delete op.is_home;
|
||
|
||
// 合并新条件
|
||
$.extend(filter, filterObj);
|
||
$.extend(op, opObj);
|
||
|
||
p.filter = JSON.stringify(filter);
|
||
p.op = JSON.stringify(op);
|
||
return p;
|
||
};
|
||
table.bootstrapTable('refresh', {});
|
||
}
|
||
|
||
// 全部
|
||
$(document).on("click", ".btn-filter-all", function () {
|
||
applyQuickFilter({}, {}, this);
|
||
});
|
||
|
||
// 仅看一级大类 (pid = 0)
|
||
$(document).on("click", ".btn-filter-top", function () {
|
||
applyQuickFilter({pid: 0}, {pid: '='}, this);
|
||
});
|
||
|
||
// 仅看二级品类 (pid > 0)
|
||
$(document).on("click", ".btn-filter-sub", function () {
|
||
applyQuickFilter({pid: 0}, {pid: '>'}, this);
|
||
});
|
||
|
||
// 快捷筛选:只看首页金刚区推荐分类 (is_home = 1)
|
||
$(document).on("click", ".btn-filter-home", function () {
|
||
applyQuickFilter({is_home: 1}, {is_home: '='}, this);
|
||
});
|
||
},
|
||
|
||
add: function () {
|
||
Controller.api.bindFormInteractions();
|
||
Controller.api.bindevent();
|
||
},
|
||
|
||
edit: function () {
|
||
Controller.api.bindFormInteractions();
|
||
Controller.api.bindevent();
|
||
},
|
||
|
||
api: {
|
||
bindevent: function () {
|
||
Form.api.bindevent($("form[role=form]"));
|
||
},
|
||
|
||
/**
|
||
* 绑定新增与编辑表单的高级可视化交互
|
||
*/
|
||
bindFormInteractions: function () {
|
||
var $iconInput = $('#c-icon_key');
|
||
var $nameInput = $('#c-name');
|
||
var $colorInput = $('#c-color');
|
||
var $colorNative = $('#c-color-native');
|
||
var $colorAddon = $('#color-preview-addon');
|
||
var $previewBadge = $('#preview-badge');
|
||
var $previewIcon = $('#preview-icon');
|
||
var $previewName = $('#preview-name');
|
||
var $previewKind = $('#preview-kind');
|
||
var $currentIconImg = $('#current-icon-img');
|
||
var $currentIconLabel = $('#current-icon-label');
|
||
var $iconModal = $('#fc-icon-modal');
|
||
var $iconSearch = $('#fc-icon-search');
|
||
var $iconTabs = $('.fc-icon-tab-btn');
|
||
|
||
// 刷新界面预览元素
|
||
function updateLivePreview() {
|
||
var name = $nameInput.val() || '未命名分类';
|
||
var key = $iconInput.val() || 'cat-job';
|
||
var color = $colorInput.val() || '#00E070';
|
||
var iconUrl = resolveIconUrl(key);
|
||
var kind = $('input[name="row[kind]"]:checked').val() || 'general';
|
||
|
||
var $matchedChip = $('.fc-icon-chip[data-key="' + key + '"]');
|
||
if ($matchedChip.length) {
|
||
var chipImg = $matchedChip.find('img').attr('src');
|
||
if (chipImg) {
|
||
iconUrl = chipImg;
|
||
}
|
||
}
|
||
|
||
if ($previewName.length) $previewName.text(name);
|
||
if ($previewIcon.length) $previewIcon.attr('src', iconUrl);
|
||
if ($currentIconImg.length) $currentIconImg.attr('src', iconUrl);
|
||
if ($currentIconLabel.length) {
|
||
if ($matchedChip.length) {
|
||
$currentIconLabel.text($matchedChip.data('name') || key);
|
||
} else if (/^(\/uploads\/|https?:\/\/)/i.test(key)) {
|
||
$currentIconLabel.text('自定义上传图片');
|
||
} else {
|
||
$currentIconLabel.text(key);
|
||
}
|
||
}
|
||
|
||
if ($previewKind.length) {
|
||
if (kind === 'farm') {
|
||
$previewKind.text('农产品类目').removeClass('general').addClass('farm');
|
||
} else {
|
||
$previewKind.text('通用便民类目').removeClass('farm').addClass('general');
|
||
}
|
||
}
|
||
|
||
if ($previewBadge.length) {
|
||
$previewBadge.css({
|
||
'background': color + '1a',
|
||
'border-color': color + '4d',
|
||
'box-shadow': '0 6px 16px ' + color + '26'
|
||
});
|
||
}
|
||
if ($colorAddon.length) $colorAddon.css('background', color);
|
||
if ($colorNative.length) $colorNative.val(color);
|
||
}
|
||
|
||
// 确保初始状态完全隐藏
|
||
$iconModal.removeClass('open').attr('style', 'display:none !important;');
|
||
|
||
// 1. 打开插画挑选面板
|
||
$(document).on('click', '#btn-open-icon-picker', function (e) {
|
||
e.preventDefault();
|
||
e.stopPropagation();
|
||
$iconModal.removeAttr('style').addClass('open').fadeIn(150);
|
||
// 打开时定位高亮当前图标
|
||
var curKey = $iconInput.val();
|
||
var $activeChip = $('.fc-icon-chip[data-key="' + curKey + '"]');
|
||
$('.fc-icon-chip').removeClass('active');
|
||
if ($activeChip.length) {
|
||
$activeChip.addClass('active');
|
||
}
|
||
if ($iconSearch.length) {
|
||
$iconSearch.focus();
|
||
}
|
||
});
|
||
|
||
// 关闭插画挑选面板
|
||
function closeIconModal() {
|
||
$iconModal.removeClass('open').fadeOut(120, function () {
|
||
$(this).attr('style', 'display:none !important;');
|
||
});
|
||
}
|
||
|
||
$(document).on('click', '#btn-close-icon-modal, .fc-modal-overlay', function (e) {
|
||
e.preventDefault();
|
||
closeIconModal();
|
||
});
|
||
|
||
// 2. 点选图标卡片
|
||
$(document).on('click', '.fc-icon-chip', function (e) {
|
||
e.preventDefault();
|
||
var key = $(this).data('key');
|
||
var iconName = $(this).data('name') || '';
|
||
var iconColor = $(this).data('color') || '';
|
||
|
||
$iconInput.val(key).trigger('change');
|
||
$('.fc-icon-chip').removeClass('active');
|
||
$(this).addClass('active');
|
||
|
||
// 若名称尚未输入,自动以图标名称填充
|
||
if ($nameInput.length && !$nameInput.val().trim() && iconName) {
|
||
$nameInput.val(iconName).trigger('input');
|
||
}
|
||
|
||
// 若当前主题色为默认春绿且图标带有推荐色,可智能联动
|
||
if (iconColor && ($colorInput.val() === '#00E070' || !$colorInput.val())) {
|
||
$colorInput.val(iconColor).trigger('change');
|
||
}
|
||
|
||
updateLivePreview();
|
||
closeIconModal();
|
||
});
|
||
|
||
// 2.1 图标分类 Tab 切换
|
||
$(document).on('click', '.fc-icon-tab-btn', function (e) {
|
||
e.preventDefault();
|
||
$('.fc-icon-tab-btn').removeClass('active');
|
||
$(this).addClass('active');
|
||
var targetGroup = $(this).data('group');
|
||
filterChips();
|
||
});
|
||
|
||
// 2.2 图标搜索过滤
|
||
if ($iconSearch.length) {
|
||
$iconSearch.on('input', function () {
|
||
filterChips();
|
||
});
|
||
}
|
||
|
||
// 过滤卡片执行逻辑
|
||
function filterChips() {
|
||
var group = $('.fc-icon-tab-btn.active').data('group') || 'all';
|
||
var keyword = ($iconSearch.val() || '').trim().toLowerCase();
|
||
var visibleCount = 0;
|
||
|
||
$('.fc-icon-chip').each(function () {
|
||
var chipGroup = $(this).data('group') || '';
|
||
var chipKey = ($(this).data('key') || '').toLowerCase();
|
||
var chipName = ($(this).data('name') || '').toLowerCase();
|
||
|
||
var matchGroup = (group === 'all' || chipGroup === group);
|
||
var matchKeyword = (!keyword || chipName.indexOf(keyword) !== -1 || chipKey.indexOf(keyword) !== -1);
|
||
|
||
if (matchGroup && matchKeyword) {
|
||
$(this).show();
|
||
visibleCount++;
|
||
} else {
|
||
$(this).hide();
|
||
}
|
||
});
|
||
|
||
var $emptyTip = $('#fc-icon-empty');
|
||
if (visibleCount === 0) {
|
||
if (!$emptyTip.length) {
|
||
$('.fc-modal-grid').append('<div id="fc-icon-empty" style="width:100%;text-align:center;padding:32px 10px;color:#94a3b8;font-size:13px;"><i class="fa fa-folder-open-o" style="font-size:24px;display:block;margin-bottom:8px;color:#cbd5e1;"></i>未找到匹配的预设图标,您可直接使用「上传自定义图标」</div>');
|
||
} else {
|
||
$emptyTip.show();
|
||
}
|
||
} else if ($emptyTip.length) {
|
||
$emptyTip.hide();
|
||
}
|
||
}
|
||
|
||
// 3. 自定义上传图片监听
|
||
$iconInput.on('input change', function () {
|
||
updateLivePreview();
|
||
});
|
||
|
||
// 4. 监听分类名称输入
|
||
$nameInput.on('input', function () {
|
||
updateLivePreview();
|
||
});
|
||
|
||
// 4.1 监听分类类型单选变更
|
||
$('input[name="row[kind]"]').on('change', function () {
|
||
updateLivePreview();
|
||
});
|
||
|
||
// 5. 监听颜色输入与推荐调色板点选
|
||
$colorInput.on('input change', function () {
|
||
updateLivePreview();
|
||
});
|
||
|
||
// 5.1 原生取色器 → 十六进制输入框
|
||
$colorNative.on('input change', function () {
|
||
$colorInput.val($(this).val()).trigger('change');
|
||
});
|
||
|
||
$(document).on('click', '.fc-color-presets .fc-color-dot', function () {
|
||
var color = $(this).data('color');
|
||
$colorInput.val(color).trigger('change');
|
||
updateLivePreview();
|
||
});
|
||
|
||
// 初始化回显
|
||
var initKey = $iconInput.val() || 'cat-service';
|
||
$('.fc-icon-chip[data-key="' + initKey + '"]').addClass('active');
|
||
updateLivePreview();
|
||
}
|
||
}
|
||
};
|
||
return Controller;
|
||
});
|