2026-04-30 15:28:20 +08:00
|
|
|
|
using System.Collections.ObjectModel;
|
|
|
|
|
|
using System.Linq;
|
2026-05-18 15:55:11 +08:00
|
|
|
|
using System.Windows;
|
2026-04-30 15:28:20 +08:00
|
|
|
|
using HandyControl.Controls;
|
|
|
|
|
|
using Prism.Commands;
|
|
|
|
|
|
using Prism.Mvvm;
|
|
|
|
|
|
using YY.Admin.Core;
|
|
|
|
|
|
using YY.Admin.Core.Extension;
|
|
|
|
|
|
using YY.Admin.Event;
|
|
|
|
|
|
using YY.Admin.Services.Service.Menu;
|
|
|
|
|
|
|
|
|
|
|
|
namespace YY.Admin.ViewModels.SysManage;
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2026-05-18 15:55:11 +08:00
|
|
|
|
/// 左侧列表一行(树形扁平展示,可见行受折叠状态控制)
|
2026-04-30 15:28:20 +08:00
|
|
|
|
/// </summary>
|
|
|
|
|
|
public sealed class MenuFlatRow
|
|
|
|
|
|
{
|
|
|
|
|
|
public SysMenu Menu { get; }
|
|
|
|
|
|
public int Depth { get; }
|
2026-05-18 15:55:11 +08:00
|
|
|
|
/// <summary>是否存在子节点(用于显示展开按钮)</summary>
|
|
|
|
|
|
public bool HasChildren { get; }
|
|
|
|
|
|
/// <summary>子节点当前是否展开</summary>
|
|
|
|
|
|
public bool IsExpanded { get; }
|
|
|
|
|
|
public Thickness LeadingMargin => new(Depth * 14, 0, 0, 0);
|
|
|
|
|
|
public string TitleText { get; }
|
2026-04-30 15:28:20 +08:00
|
|
|
|
|
2026-05-18 15:55:11 +08:00
|
|
|
|
public MenuFlatRow(SysMenu menu, int depth, bool hasChildren, bool isExpanded)
|
2026-04-30 15:28:20 +08:00
|
|
|
|
{
|
|
|
|
|
|
Menu = menu;
|
|
|
|
|
|
Depth = depth;
|
2026-05-18 15:55:11 +08:00
|
|
|
|
HasChildren = hasChildren;
|
|
|
|
|
|
IsExpanded = isExpanded;
|
2026-04-30 15:28:20 +08:00
|
|
|
|
var tag = menu.Type == MenuTypeEnum.Dir ? "[目录] " : menu.Type == MenuTypeEnum.Btn ? "[按钮] " : "[菜单] ";
|
2026-05-18 15:55:11 +08:00
|
|
|
|
var homeMark = menu.IsDefaultDesktopHome ? "[默认首页] " : "";
|
|
|
|
|
|
TitleText = tag + homeMark + menu.Title;
|
2026-04-30 15:28:20 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 可编辑字段(与界面绑定)
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public class MenuEditorModel : BindableBase
|
|
|
|
|
|
{
|
|
|
|
|
|
private long _id;
|
|
|
|
|
|
private long _pid;
|
|
|
|
|
|
private MenuTypeEnum _type = MenuTypeEnum.Menu;
|
|
|
|
|
|
private string? _name;
|
|
|
|
|
|
private string? _path;
|
|
|
|
|
|
private string? _component;
|
|
|
|
|
|
private string? _redirect;
|
|
|
|
|
|
private string? _permission;
|
|
|
|
|
|
private string _title = string.Empty;
|
|
|
|
|
|
private string? _icon;
|
|
|
|
|
|
private bool _isIframe;
|
|
|
|
|
|
private string? _outLink;
|
|
|
|
|
|
private bool _isHide;
|
|
|
|
|
|
private bool _isKeepAlive = true;
|
|
|
|
|
|
private bool _isAffix;
|
2026-05-18 15:55:11 +08:00
|
|
|
|
private bool _isDefaultDesktopHome;
|
2026-04-30 15:28:20 +08:00
|
|
|
|
private int _orderNo = 100;
|
|
|
|
|
|
private StatusEnum _status = StatusEnum.Enable;
|
|
|
|
|
|
private string? _remark;
|
|
|
|
|
|
|
|
|
|
|
|
public long Id { get => _id; set => SetProperty(ref _id, value); }
|
|
|
|
|
|
public long Pid { get => _pid; set => SetProperty(ref _pid, value); }
|
2026-05-18 15:55:11 +08:00
|
|
|
|
public MenuTypeEnum Type
|
|
|
|
|
|
{
|
|
|
|
|
|
get => _type;
|
|
|
|
|
|
set
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!SetProperty(ref _type, value))
|
|
|
|
|
|
return;
|
|
|
|
|
|
RaisePropertyChanged(nameof(CanSetDefaultDesktopHome));
|
|
|
|
|
|
if (value != MenuTypeEnum.Menu)
|
|
|
|
|
|
IsDefaultDesktopHome = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-04-30 15:28:20 +08:00
|
|
|
|
public string? Name { get => _name; set => SetProperty(ref _name, value); }
|
|
|
|
|
|
public string? Path { get => _path; set => SetProperty(ref _path, value); }
|
|
|
|
|
|
public string? Component { get => _component; set => SetProperty(ref _component, value); }
|
|
|
|
|
|
public string? Redirect { get => _redirect; set => SetProperty(ref _redirect, value); }
|
|
|
|
|
|
public string? Permission { get => _permission; set => SetProperty(ref _permission, value); }
|
|
|
|
|
|
public string Title { get => _title; set => SetProperty(ref _title, value); }
|
|
|
|
|
|
public string? Icon { get => _icon; set => SetProperty(ref _icon, value); }
|
|
|
|
|
|
public bool IsIframe { get => _isIframe; set => SetProperty(ref _isIframe, value); }
|
|
|
|
|
|
public string? OutLink { get => _outLink; set => SetProperty(ref _outLink, value); }
|
|
|
|
|
|
public bool IsHide { get => _isHide; set => SetProperty(ref _isHide, value); }
|
|
|
|
|
|
public bool IsKeepAlive { get => _isKeepAlive; set => SetProperty(ref _isKeepAlive, value); }
|
|
|
|
|
|
public bool IsAffix { get => _isAffix; set => SetProperty(ref _isAffix, value); }
|
2026-05-18 15:55:11 +08:00
|
|
|
|
public bool IsDefaultDesktopHome
|
|
|
|
|
|
{
|
|
|
|
|
|
get => _isDefaultDesktopHome;
|
|
|
|
|
|
set => SetProperty(ref _isDefaultDesktopHome, value);
|
|
|
|
|
|
}
|
2026-04-30 15:28:20 +08:00
|
|
|
|
public int OrderNo { get => _orderNo; set => SetProperty(ref _orderNo, value); }
|
|
|
|
|
|
public StatusEnum Status { get => _status; set => SetProperty(ref _status, value); }
|
|
|
|
|
|
public string? Remark { get => _remark; set => SetProperty(ref _remark, value); }
|
|
|
|
|
|
|
|
|
|
|
|
public bool IsNew => Id == 0;
|
|
|
|
|
|
|
2026-05-18 15:55:11 +08:00
|
|
|
|
/// <summary>仅「菜单」类型可设为桌面默认首页</summary>
|
|
|
|
|
|
public bool CanSetDefaultDesktopHome => Type == MenuTypeEnum.Menu;
|
|
|
|
|
|
|
2026-04-30 15:28:20 +08:00
|
|
|
|
public void LoadFrom(SysMenu m)
|
|
|
|
|
|
{
|
|
|
|
|
|
Id = m.Id;
|
|
|
|
|
|
Pid = m.Pid;
|
|
|
|
|
|
Type = m.Type;
|
|
|
|
|
|
Name = m.Name;
|
|
|
|
|
|
Path = m.Path;
|
|
|
|
|
|
Component = m.Component;
|
|
|
|
|
|
Redirect = m.Redirect;
|
|
|
|
|
|
Permission = m.Permission;
|
|
|
|
|
|
Title = m.Title;
|
|
|
|
|
|
Icon = m.Icon;
|
|
|
|
|
|
IsIframe = m.IsIframe;
|
|
|
|
|
|
OutLink = m.OutLink;
|
|
|
|
|
|
IsHide = m.IsHide;
|
|
|
|
|
|
IsKeepAlive = m.IsKeepAlive;
|
|
|
|
|
|
IsAffix = m.IsAffix;
|
2026-05-18 15:55:11 +08:00
|
|
|
|
IsDefaultDesktopHome = m.Type == MenuTypeEnum.Menu && m.IsDefaultDesktopHome;
|
2026-04-30 15:28:20 +08:00
|
|
|
|
OrderNo = m.OrderNo;
|
|
|
|
|
|
Status = m.Status;
|
|
|
|
|
|
Remark = m.Remark;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public SysMenu ToSysMenu()
|
|
|
|
|
|
{
|
|
|
|
|
|
return new SysMenu
|
|
|
|
|
|
{
|
|
|
|
|
|
Id = Id,
|
|
|
|
|
|
Pid = Pid,
|
|
|
|
|
|
Type = Type,
|
|
|
|
|
|
Name = Name,
|
|
|
|
|
|
Path = Path,
|
|
|
|
|
|
Component = Component,
|
|
|
|
|
|
Redirect = Redirect,
|
|
|
|
|
|
Permission = Permission,
|
|
|
|
|
|
Title = Title.Trim(),
|
|
|
|
|
|
Icon = Icon,
|
|
|
|
|
|
IsIframe = IsIframe,
|
|
|
|
|
|
OutLink = OutLink,
|
|
|
|
|
|
IsHide = IsHide,
|
|
|
|
|
|
IsKeepAlive = IsKeepAlive,
|
|
|
|
|
|
IsAffix = IsAffix,
|
2026-05-18 15:55:11 +08:00
|
|
|
|
IsDefaultDesktopHome = Type == MenuTypeEnum.Menu && IsDefaultDesktopHome,
|
2026-04-30 15:28:20 +08:00
|
|
|
|
OrderNo = OrderNo,
|
|
|
|
|
|
Status = Status,
|
|
|
|
|
|
Remark = Remark
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void ResetForNew(long pid, MenuTypeEnum type)
|
|
|
|
|
|
{
|
|
|
|
|
|
Id = 0;
|
|
|
|
|
|
Pid = pid;
|
|
|
|
|
|
Type = type;
|
|
|
|
|
|
Name = null;
|
|
|
|
|
|
Path = null;
|
|
|
|
|
|
Component = null;
|
|
|
|
|
|
Redirect = null;
|
|
|
|
|
|
Permission = null;
|
|
|
|
|
|
Title = type == MenuTypeEnum.Dir ? "新目录" : type == MenuTypeEnum.Menu ? "新菜单" : "新按钮";
|
|
|
|
|
|
Icon = "";
|
|
|
|
|
|
IsIframe = false;
|
|
|
|
|
|
OutLink = null;
|
|
|
|
|
|
IsHide = false;
|
|
|
|
|
|
IsKeepAlive = true;
|
|
|
|
|
|
IsAffix = false;
|
2026-05-18 15:55:11 +08:00
|
|
|
|
IsDefaultDesktopHome = false;
|
2026-04-30 15:28:20 +08:00
|
|
|
|
OrderNo = 100;
|
|
|
|
|
|
Status = StatusEnum.Enable;
|
|
|
|
|
|
Remark = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public class MenuManagementViewModel : BaseViewModel
|
|
|
|
|
|
{
|
|
|
|
|
|
private readonly ISysMenuService _menuService;
|
2026-05-18 15:55:11 +08:00
|
|
|
|
/// <summary>最近一次加载的全量菜单(折叠切换时仅重算列表,不重复读库)</summary>
|
|
|
|
|
|
private List<SysMenu> _allMenusCache = new();
|
|
|
|
|
|
/// <summary>已折叠节点 Id(其子级不在扁平列表中展示)</summary>
|
|
|
|
|
|
private readonly HashSet<long> _collapsedMenuIds = new();
|
2026-04-30 15:28:20 +08:00
|
|
|
|
|
|
|
|
|
|
public ObservableCollection<MenuFlatRow> FlatRows { get; } = new();
|
|
|
|
|
|
public ObservableCollection<KeyValuePair<long, string>> ParentOptions { get; } = new();
|
|
|
|
|
|
|
|
|
|
|
|
private MenuFlatRow? _selectedRow;
|
|
|
|
|
|
public MenuFlatRow? SelectedRow
|
|
|
|
|
|
{
|
|
|
|
|
|
get => _selectedRow;
|
|
|
|
|
|
set
|
|
|
|
|
|
{
|
|
|
|
|
|
if (SetProperty(ref _selectedRow, value))
|
|
|
|
|
|
{
|
|
|
|
|
|
if (value == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
Editor = null;
|
|
|
|
|
|
RaisePropertyChanged(nameof(Editor));
|
|
|
|
|
|
RaisePropertyChanged(nameof(HasEditor));
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
Editor ??= new MenuEditorModel();
|
|
|
|
|
|
Editor.LoadFrom(value.Menu);
|
|
|
|
|
|
RaisePropertyChanged(nameof(Editor));
|
|
|
|
|
|
RaisePropertyChanged(nameof(HasEditor));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private MenuEditorModel? _editor;
|
|
|
|
|
|
public MenuEditorModel? Editor
|
|
|
|
|
|
{
|
|
|
|
|
|
get => _editor;
|
|
|
|
|
|
set
|
|
|
|
|
|
{
|
|
|
|
|
|
if (SetProperty(ref _editor, value))
|
|
|
|
|
|
{
|
|
|
|
|
|
RaisePropertyChanged(nameof(HasEditor));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public bool HasEditor => Editor != null;
|
|
|
|
|
|
|
|
|
|
|
|
public List<KeyValuePair<string, int>> StatusList =>
|
|
|
|
|
|
Enum.GetValues(typeof(StatusEnum))
|
|
|
|
|
|
.Cast<StatusEnum>()
|
|
|
|
|
|
.Select(e => new KeyValuePair<string, int>(e.GetDescription(), (int)e))
|
|
|
|
|
|
.ToList();
|
|
|
|
|
|
|
|
|
|
|
|
public List<KeyValuePair<string, int>> MenuTypeList =>
|
|
|
|
|
|
Enum.GetValues(typeof(MenuTypeEnum))
|
|
|
|
|
|
.Cast<MenuTypeEnum>()
|
|
|
|
|
|
.Select(e => new KeyValuePair<string, int>(e.GetDescription(), (int)e))
|
|
|
|
|
|
.ToList();
|
|
|
|
|
|
|
|
|
|
|
|
public DelegateCommand RefreshCommand { get; }
|
|
|
|
|
|
public DelegateCommand AddRootCommand { get; }
|
|
|
|
|
|
public DelegateCommand AddChildCommand { get; }
|
|
|
|
|
|
public DelegateCommand SaveCommand { get; }
|
|
|
|
|
|
public DelegateCommand DeleteCommand { get; }
|
2026-05-18 15:55:11 +08:00
|
|
|
|
public DelegateCommand<MenuFlatRow?> ToggleExpandCommand { get; }
|
|
|
|
|
|
public DelegateCommand ExpandAllCommand { get; }
|
|
|
|
|
|
public DelegateCommand CollapseAllCommand { get; }
|
2026-04-30 15:28:20 +08:00
|
|
|
|
|
|
|
|
|
|
public MenuManagementViewModel(
|
|
|
|
|
|
ISysMenuService menuService,
|
|
|
|
|
|
IContainerExtension container,
|
|
|
|
|
|
IRegionManager regionManager) : base(container, regionManager)
|
|
|
|
|
|
{
|
|
|
|
|
|
_menuService = menuService;
|
|
|
|
|
|
|
|
|
|
|
|
RefreshCommand = new DelegateCommand(async () => await RefreshAsync());
|
|
|
|
|
|
AddRootCommand = new DelegateCommand(() => BeginNew(0, MenuTypeEnum.Dir));
|
|
|
|
|
|
AddChildCommand = new DelegateCommand(BeginNewChild, () => SelectedRow != null)
|
|
|
|
|
|
.ObservesProperty(() => SelectedRow);
|
|
|
|
|
|
SaveCommand = new DelegateCommand(async () => await SaveAsync(), () => Editor != null)
|
|
|
|
|
|
.ObservesProperty(() => Editor);
|
|
|
|
|
|
DeleteCommand = new DelegateCommand(async () => await DeleteAsync(), () => SelectedRow != null && SelectedRow.Menu.Id != 0)
|
|
|
|
|
|
.ObservesProperty(() => SelectedRow);
|
2026-05-18 15:55:11 +08:00
|
|
|
|
ToggleExpandCommand = new DelegateCommand<MenuFlatRow?>(OnToggleExpand);
|
|
|
|
|
|
ExpandAllCommand = new DelegateCommand(OnExpandAll);
|
|
|
|
|
|
CollapseAllCommand = new DelegateCommand(OnCollapseAll);
|
2026-04-30 15:28:20 +08:00
|
|
|
|
|
|
|
|
|
|
_ = RefreshAsync();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private async Task RefreshAsync()
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
IsLoading = true;
|
|
|
|
|
|
var all = await _menuService.GetAllMenusForManageAsync();
|
2026-05-18 15:55:11 +08:00
|
|
|
|
PruneCollapsedState(all);
|
|
|
|
|
|
_allMenusCache = all;
|
2026-04-30 15:28:20 +08:00
|
|
|
|
RebuildFlat(all);
|
|
|
|
|
|
RebuildParentOptions(all);
|
2026-05-18 15:55:11 +08:00
|
|
|
|
ResyncSelectionAfterRebuild();
|
2026-04-30 15:28:20 +08:00
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
Growl.Error($"加载菜单失败:{ex.Message}");
|
|
|
|
|
|
}
|
|
|
|
|
|
finally
|
|
|
|
|
|
{
|
|
|
|
|
|
IsLoading = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void RebuildFlat(List<SysMenu> all)
|
|
|
|
|
|
{
|
|
|
|
|
|
FlatRows.Clear();
|
2026-05-18 15:55:11 +08:00
|
|
|
|
bool HasChild(long id) => all.Any(x => x.Pid == id);
|
|
|
|
|
|
|
2026-04-30 15:28:20 +08:00
|
|
|
|
void Walk(long pid, int depth)
|
|
|
|
|
|
{
|
|
|
|
|
|
foreach (var m in all.Where(x => x.Pid == pid).OrderBy(x => x.OrderNo).ThenBy(x => x.Id))
|
|
|
|
|
|
{
|
2026-05-18 15:55:11 +08:00
|
|
|
|
var hasCh = HasChild(m.Id);
|
|
|
|
|
|
var expanded = !_collapsedMenuIds.Contains(m.Id);
|
|
|
|
|
|
FlatRows.Add(new MenuFlatRow(m, depth, hasCh, expanded));
|
|
|
|
|
|
if (hasCh && expanded)
|
|
|
|
|
|
Walk(m.Id, depth + 1);
|
2026-04-30 15:28:20 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Walk(0, 0);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-18 15:55:11 +08:00
|
|
|
|
/// <summary>删除已不存在的菜单 Id,并去掉已无子级的折叠记录</summary>
|
|
|
|
|
|
private void PruneCollapsedState(List<SysMenu> all)
|
|
|
|
|
|
{
|
|
|
|
|
|
var validIds = new HashSet<long>(all.Select(m => m.Id));
|
|
|
|
|
|
foreach (var id in _collapsedMenuIds.ToList())
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!validIds.Contains(id) || !all.Any(x => x.Pid == id))
|
|
|
|
|
|
_collapsedMenuIds.Remove(id);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void OnToggleExpand(MenuFlatRow? row)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (row?.HasChildren != true)
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
|
|
var id = row.Menu.Id;
|
|
|
|
|
|
if (_collapsedMenuIds.Contains(id))
|
|
|
|
|
|
_collapsedMenuIds.Remove(id);
|
|
|
|
|
|
else
|
|
|
|
|
|
_collapsedMenuIds.Add(id);
|
|
|
|
|
|
|
|
|
|
|
|
RebuildFlat(_allMenusCache);
|
|
|
|
|
|
ResyncSelectionAfterRebuild();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void OnExpandAll()
|
|
|
|
|
|
{
|
|
|
|
|
|
_collapsedMenuIds.Clear();
|
|
|
|
|
|
RebuildFlat(_allMenusCache);
|
|
|
|
|
|
ResyncSelectionAfterRebuild();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void OnCollapseAll()
|
|
|
|
|
|
{
|
|
|
|
|
|
_collapsedMenuIds.Clear();
|
|
|
|
|
|
foreach (var m in _allMenusCache.Where(m => _allMenusCache.Any(c => c.Pid == m.Id)))
|
|
|
|
|
|
_collapsedMenuIds.Add(m.Id);
|
|
|
|
|
|
|
|
|
|
|
|
RebuildFlat(_allMenusCache);
|
|
|
|
|
|
ResyncSelectionAfterRebuild();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>重建列表后,按 Id 恢复选中;若当前项被折叠隐藏则选中其可见祖先</summary>
|
|
|
|
|
|
private void ResyncSelectionAfterRebuild()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_selectedRow == null)
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
|
|
var id = _selectedRow.Menu.Id;
|
|
|
|
|
|
MenuFlatRow? row = FlatRows.FirstOrDefault(r => r.Menu.Id == id);
|
|
|
|
|
|
var curId = id;
|
|
|
|
|
|
while (row == null && curId != 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
var m = _allMenusCache.FirstOrDefault(x => x.Id == curId);
|
|
|
|
|
|
if (m == null)
|
|
|
|
|
|
break;
|
|
|
|
|
|
curId = m.Pid;
|
|
|
|
|
|
if (curId == 0)
|
|
|
|
|
|
break;
|
|
|
|
|
|
row = FlatRows.FirstOrDefault(r => r.Menu.Id == curId);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (row != null)
|
|
|
|
|
|
SelectedRow = row;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-30 15:28:20 +08:00
|
|
|
|
private void RebuildParentOptions(List<SysMenu> all)
|
|
|
|
|
|
{
|
|
|
|
|
|
ParentOptions.Clear();
|
|
|
|
|
|
ParentOptions.Add(new KeyValuePair<long, string>(0, "(根目录)"));
|
|
|
|
|
|
void Walk(long pid, int depth)
|
|
|
|
|
|
{
|
|
|
|
|
|
foreach (var m in all.Where(x => x.Pid == pid).OrderBy(x => x.OrderNo).ThenBy(x => x.Id))
|
|
|
|
|
|
{
|
|
|
|
|
|
var indent = new string(' ', depth);
|
|
|
|
|
|
ParentOptions.Add(new KeyValuePair<long, string>(m.Id, indent + m.Title));
|
|
|
|
|
|
Walk(m.Id, depth + 1);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Walk(0, 0);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void BeginNew(long pid, MenuTypeEnum type)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 先取消列表选中,避免 setter 把新建的 Editor 清空
|
|
|
|
|
|
_selectedRow = null;
|
|
|
|
|
|
RaisePropertyChanged(nameof(SelectedRow));
|
|
|
|
|
|
Editor = new MenuEditorModel();
|
|
|
|
|
|
Editor.ResetForNew(pid, type);
|
|
|
|
|
|
RaisePropertyChanged(nameof(Editor));
|
|
|
|
|
|
RaisePropertyChanged(nameof(HasEditor));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void BeginNewChild()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (SelectedRow == null)
|
|
|
|
|
|
return;
|
|
|
|
|
|
BeginNew(SelectedRow.Menu.Id, MenuTypeEnum.Menu);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private async Task SaveAsync()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (Editor == null)
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
IsLoading = true;
|
|
|
|
|
|
var entity = Editor.ToSysMenu();
|
|
|
|
|
|
if (Editor.IsNew)
|
|
|
|
|
|
{
|
|
|
|
|
|
var (ok, msg, id) = await _menuService.CreateMenuAsync(entity);
|
|
|
|
|
|
if (ok)
|
|
|
|
|
|
{
|
|
|
|
|
|
Growl.Success(msg);
|
|
|
|
|
|
_eventAggregator.GetEvent<MenuStructureChangedEvent>().Publish(true);
|
|
|
|
|
|
await RefreshAsync();
|
|
|
|
|
|
SelectedRow = FlatRows.FirstOrDefault(r => r.Menu.Id == id);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
Growl.Warning(msg);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
var (ok, msg) = await _menuService.UpdateMenuAsync(entity);
|
|
|
|
|
|
if (ok)
|
|
|
|
|
|
{
|
|
|
|
|
|
Growl.Success(msg);
|
|
|
|
|
|
_eventAggregator.GetEvent<MenuStructureChangedEvent>().Publish(true);
|
|
|
|
|
|
var keepId = entity.Id;
|
|
|
|
|
|
await RefreshAsync();
|
|
|
|
|
|
SelectedRow = FlatRows.FirstOrDefault(r => r.Menu.Id == keepId);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
Growl.Warning(msg);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
Growl.Error($"保存失败:{ex.Message}");
|
|
|
|
|
|
}
|
|
|
|
|
|
finally
|
|
|
|
|
|
{
|
|
|
|
|
|
IsLoading = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private async Task DeleteAsync()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (SelectedRow == null)
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
|
|
var r = System.Windows.MessageBox.Show(
|
|
|
|
|
|
$"确定删除「{SelectedRow.Menu.Title}」?若存在子菜单将无法删除。",
|
|
|
|
|
|
"确认删除",
|
|
|
|
|
|
System.Windows.MessageBoxButton.OKCancel,
|
|
|
|
|
|
System.Windows.MessageBoxImage.Question);
|
|
|
|
|
|
if (r != System.Windows.MessageBoxResult.OK)
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
IsLoading = true;
|
|
|
|
|
|
var (ok, msg) = await _menuService.DeleteMenuAsync(SelectedRow.Menu.Id);
|
|
|
|
|
|
if (ok)
|
|
|
|
|
|
{
|
|
|
|
|
|
Growl.Success(msg);
|
|
|
|
|
|
_eventAggregator.GetEvent<MenuStructureChangedEvent>().Publish(true);
|
|
|
|
|
|
Editor = null;
|
|
|
|
|
|
SelectedRow = null;
|
|
|
|
|
|
await RefreshAsync();
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
Growl.Warning(msg);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
Growl.Error($"删除失败:{ex.Message}");
|
|
|
|
|
|
}
|
|
|
|
|
|
finally
|
|
|
|
|
|
{
|
|
|
|
|
|
IsLoading = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|