classdef project < matlab.apps.AppBase
% 水果店管理系统主类(改进版)
properties (Access = public)
UIFigure matlab.ui.Figure
LoginPanel matlab.ui.container.Panel
MainPanel matlab.ui.container.Panel
UsernameEdit matlab.ui.control.EditField
PasswordEdit matlab.ui.control.EditField
LoginButton matlab.ui.control.Button
DataTable matlab.ui.control.Table
OptimizeButton matlab.ui.control.Button
ProfitChart matlab.ui.control.UIAxes
ResultDisplay matlab.ui.control.TextArea
BudgetEdit matlab.ui.control.NumericEditField
StorageEdit matlab.ui.control.NumericEditField
SalesHistoryTable matlab.ui.control.Table
AddSaleButton matlab.ui.control.Button
SaleFruitDropdown matlab.ui.control.DropDown
SaleQuantityEdit matlab.ui.control.NumericEditField
end
properties (Access = private)
UserCredentials = containers.Map({'admin'}, {'123456'})
FruitData = struct(...
'Name', {{'苹果','香蕉','橙子','葡萄'}},...
'Cost', {[8, 5, 6, 12]},...
'Price', {[12, 8, 10, 18]},...
'Stock', {[50, 30, 40, 20]},...
'LossRate', {[0.05, 0.1, 0.03, 0.02]},...
'ShelfLife', {[7, 5, 10, 3]}... % 保质期(天)
)
SalesHistory = struct('Date', {}, 'Fruit', {}, 'Quantity', {}, 'Revenue', {}, 'Cost', {}, 'Profit', {})
OptimizationResult = []
GAParams = struct('PopSize', 100, 'MaxGen', 200, 'CrossoverProb', 0.8, 'MutationProb', 0.1)
end
methods (Access = public)
function app = project()
createUIComponents(app);
end
end
methods (Access = private)
%% UI组件创建
function createUIComponents(app)
% 主窗口
app.UIFigure = uifigure('Name', '水果店管理系统', 'Position', [100 100 1000 700]);
% 登录面板
app.LoginPanel = uipanel(app.UIFigure, 'Position', [350 250 300 200], 'Title', '系统登录');
uilabel(app.LoginPanel, 'Text', '用户名:', 'Position', [30 130 80 22]);
app.UsernameEdit = uieditfield(app.LoginPanel, 'text', 'Position', [120 130 150 22]);
uilabel(app.LoginPanel, 'Text', '密码:', 'Position', [30 90 80 22]);
app.PasswordEdit = uieditfield(app.LoginPanel, 'text', 'Password', '****', 'Position', [120 90 150 22]);
app.LoginButton = uibutton(app.LoginPanel, 'push', 'Text', '登录', ...
'Position', [110 30 80 30], 'ButtonPushedFcn', @(src,event) loginCallback(app, src, event));
% 主面板(初始隐藏)
app.MainPanel = uipanel(app.UIFigure, 'Position', [20 20 960 660], 'Title', '水果店管理系统', 'Visible', 'off');
% 水果数据表格
app.DataTable = uitable(app.MainPanel, 'Position', [20 450 420 200], ...
'ColumnName', {'名称', '成本(元/kg)', '售价(元/kg)', '库存(kg)', '损耗率', '保质期(天)'}, ...
'ColumnEditable', [false true true false false false], ...
'ColumnFormat', {'char', 'numeric', 'numeric', 'numeric', 'numeric', 'numeric'});
% 销售历史表格
app.SalesHistoryTable = uitable(app.MainPanel, 'Position', [460 450 480 200], ...
'ColumnName', {'日期', '水果', '销量(kg)', '收入(元)', '成本(元)', '利润(元)'}, ...
'ColumnEditable', false);
% 利润图表
app.ProfitChart = uiaxes(app.MainPanel, 'Position', [20 250 420 180]);
title(app.ProfitChart, '历史利润趋势');
xlabel(app.ProfitChart, '日期');
ylabel(app.ProfitChart, '利润(元)');
grid(app.ProfitChart, 'on');
% 优化参数
uilabel(app.MainPanel, 'Text', '预算(元):', 'Position', [460 380 80 22]);
app.BudgetEdit = uieditfield(app.MainPanel, 'numeric', 'Value', 5000, 'Position', [550 380 100 22]);
uilabel(app.MainPanel, 'Text', '仓储容量(kg):', 'Position', [460 350 80 22]);
app.StorageEdit = uieditfield(app.MainPanel, 'numeric', 'Value', 1000, 'Position', [550 350 100 22]);
% 优化按钮
app.OptimizeButton = uibutton(app.MainPanel, 'push', 'Text', '优化进货', ...
'Position', [460 300 100 30], 'ButtonPushedFcn', @(src,event) optimizeCallback(app, src, event));
% 结果显示
app.ResultDisplay = uitextarea(app.MainPanel, 'Position', [460 200 480 80], ...
'Editable', 'off');
% 销售录入
uilabel(app.MainPanel, 'Text', '销售录入:', 'Position', [20 200 80 22], 'FontWeight', 'bold');
uilabel(app.MainPanel, 'Text', '水果:', 'Position', [20 170 80 22]);
app.SaleFruitDropdown = uidropdown(app.MainPanel, 'Items', app.FruitData.Name, ...
'Position', [100 170 120 22]);
uilabel(app.MainPanel, 'Text', '数量(kg):', 'Position', [20 140 80 22]);
app.SaleQuantityEdit = uieditfield(app.MainPanel, 'numeric', 'Position', [100 140 120 22]);
app.AddSaleButton = uibutton(app.MainPanel, 'push', 'Text', '添加销售记录', ...
'Position', [20 100 120 30], 'ButtonPushedFcn', @(src,event) addSaleRecordCallback(app, src, event));
end
%% 回调函数
function loginCallback(app, ~, ~)
username = app.UsernameEdit.Value;
password = app.PasswordEdit.Value;
if app.UserCredentials.isKey(username) && strcmp(app.UserCredentials(username), password)
app.LoginPanel.Visible = 'off';
app.MainPanel.Visible = 'on';
updateDataDisplay(app);
else
uialert(app.UIFigure, '用户名或密码错误', '登录失败');
end
end
function updateDataDisplay(app)
% 更新水果表格
fruitTable = table();
for i = 1:length(app.FruitData.Name)
fruitTable = [fruitTable; table(app.FruitData.Name{i}, app.FruitData.Cost(i), ...
app.FruitData.Price(i), app.FruitData.Stock(i), app.FruitData.LossRate{i}, ...
app.FruitData.ShelfLife{i}, 'VariableNames', ...
{'Name', 'Cost', 'Price', 'Stock', 'LossRate', 'ShelfLife'})];
end
app.DataTable.Data = fruitTable;
% 更新销售历史
if ~isempty(app.SalesHistory)
salesData = struct2table(app.SalesHistory);
app.SalesHistoryTable.Data = salesData;
% 更新利润图表
dates = unique([app.SalesHistory.Date]);
profits = zeros(size(dates));
for i = 1:length(dates)
idx = [app.SalesHistory.Date] == dates(i);
profits(i) = sum([app.SalesHistory(idx).Profit]);
end
plot(app.ProfitChart, dates, profits, 'o-', 'LineWidth', 2);
end
end
function optimizeCallback(app, ~, ~)
% 验证输入
budget = app.BudgetEdit.Value;
storageCap = app.StorageEdit.Value;
if budget <= 0 || storageCap <= 0
uialert(app.UIFigure, '预算和仓储容量必须为正数', '输入错误');
return;
end
% 获取数据
names = app.FruitData.Name;
costs = app.FruitData.Cost;
prices = app.FruitData.Price;
stock = app.FruitData.Stock;
lossRates = app.FruitData.LossRate;
shelfLives = app.FruitData.ShelfLife;
% 销售预测
salesPred = generateSalesPrediction(app);
% 优化
[bestOrder, bestProfit] = procurementOptimizationGA(app, names, costs, prices, stock, ...
lossRates, shelfLives, salesPred, budget, storageCap);
% 保存结果
app.OptimizationResult = struct('Order', bestOrder, 'Profit', bestProfit, ...
'Timestamp', datetime('now'), 'Budget', budget, 'StorageCap', storageCap);
% 显示结果
displayOptimizationResult(app);
end
function displayOptimizationResult(app)
if isempty(app.OptimizationResult)
return;
end
resultStr = sprintf('优化结果 (时间: %s)\n\n', datestr(app.OptimizationResult.Timestamp));
resultStr = [resultStr sprintf('预算: %.2f 元\n', app.OptimizationResult.Budget)];
resultStr = [resultStr sprintf('仓储容量: %.2f kg\n', app.OptimizationResult.StorageCap)];
resultStr = [resultStr sprintf('预期利润: %.2f 元\n\n', app.OptimizationResult.Profit)];
resultStr = [resultStr '推荐进货量:\n'];
names = app.FruitData.Name;
for i = 1:length(names)
resultStr = [resultStr sprintf('%s: %.2f kg\n', names{i}, app.OptimizationResult.Order(i))];
end
app.ResultDisplay.Value = resultStr;
end
function addSaleRecordCallback(app, ~, ~)
fruitIdx = app.SaleFruitDropdown.Value == app.FruitData.Name{1}; % 修正索引获取方式
fruitName = app.FruitData.Name{fruitIdx};
quantity = app.SaleQuantityEdit.Value;
if quantity <= 0
uialert(app.UIFigure, '销售数量必须为正数', '输入错误');
return;
end
try
addSalesRecord(app, datetime('today'), fruitName, quantity);
updateDataDisplay(app);
uialert(app.UIFigure, '销售记录添加成功', '成功');
catch ME
uialert(app.UIFigure, ME.message, '错误');
end
end
%% 核心功能函数
function salesPred = generateSalesPrediction(app)
if isempty(app.SalesHistory)
% 默认预测
avgSales = [20, 15, 25, 10];
else
% 基于最近5天平均销量
dates = sort([app.SalesHistory.Date]);
recentDates = dates(max(1,end-4):end);
avgSales = zeros(1, length(app.FruitData.Name));
for i = 1:length(recentDates)
idx = [app.SalesHistory.Date] == recentDates(i);
dailySales = app.SalesHistory(idx);
for j = 1:length(dailySales)
fruitIdx = find(strcmp(app.FruitData.Name, dailySales(j).Fruit));
avgSales(fruitIdx) = avgSales(fruitIdx) + dailySales(j).Quantity;
end
end
avgSales = avgSales / length(recentDates);
end
% 考虑季节性因素(示例:橙子冬季销量增加)
month = month(datetime('today'));
if month >= 11 || month <= 2 % 冬季
avgSales(3) = avgSales(3) * 1.3; % 橙子销量增加30%
end
salesPred = struct('Date', datetime('today')+1, 'Quantities', avgSales);
end
function [bestOrder, bestProfit] = procurementOptimizationGA(app, names, costs, prices, stock, ...
lossRates, shelfLives, salesPred, budget, storageCap)
nFruits = length(names);
popSize = app.GAParams.PopSize;
maxGen = app.GAParams.MaxGen;
% 初始化种群
population = initializePopulation(app, popSize, nFruits, costs, budget, stock, storageCap);
bestProfit = -Inf;
bestOrder = zeros(1, nFruits);
for gen = 1:maxGen
% 计算适应度
fitness = arrayfun(@(i) calculateFitness(app, population(i,:), costs, prices, stock, ...
lossRates, shelfLives, salesPred, budget, storageCap), 1:popSize);
% 更新最佳个体
[currentBest, idx] = max(fitness);
if currentBest > bestProfit
bestProfit = currentBest;
bestOrder = population(idx, :);
end
% 动态调整变异率
mutationProb = app.GAParams.MutationProb * (1 - 0.8*(gen/maxGen));
% 选择
selected = selection(app, population, fitness);
% 交叉
offspring = crossover(app, selected, app.GAParams.CrossoverProb);
% 变异
offspring = mutation(app, offspring, mutationProb, costs, budget, stock, storageCap);
population = offspring;
end
end
function population = initializePopulation(app, popSize, nFruits, costs, budget, stock, storageCap)
population = zeros(popSize, nFruits);
maxQuantities = min(floor(budget./costs), floor((storageCap - sum(stock))/nFruits));
for i = 1:popSize
valid = false;
while ~valid
candidate = arrayfun(@(~) randi([0, max(maxQuantities)]), 1:nFruits);
totalCost = sum(candidate .* costs);
totalStorage = sum(candidate);
if totalCost <= budget && (sum(stock) + totalStorage) <= storageCap
population(i, :) = candidate;
valid = true;
end
end
end
end
function fitness = calculateFitness(app, order, costs, prices, stock, lossRates, shelfLives, salesPred, budget, storageCap)
% 惩罚函数法处理约束
penalty = 0;
totalCost = sum(order .* costs);
if totalCost > budget
penalty = penalty + 1000 * (totalCost - budget);
end
if (sum(stock) + sum(order)) > storageCap
penalty = penalty + 1000 * ((sum(stock) + sum(order)) - storageCap);
end
% 计算利润
profit = calculateProfit(app, order, costs, prices, stock, lossRates, shelfLives, salesPred);
fitness = profit - penalty;
end
function profit = calculateProfit(app, order, costs, prices, stock, lossRates, shelfLives, salesPred)
totalCost = sum(order .* costs);
revenue = 0;
currentStock = stock + order;
% 模拟3天销售
for d = 1:3
dailyStock = currentStock;
for f = 1:length(order)
% 考虑损耗和保质期
effectiveStock = dailyStock(f) * (1 - lossRates{f});
sold = min(salesPred.Quantities(f), effectiveStock);
% 考虑保质期折扣(最后一天降价销售)
if d == 3
discount = 0.7; % 7折处理临期水果
revenue = revenue + sold * prices(f) * discount;
else
revenue = revenue + sold * prices(f);
end
% 更新库存(考虑损耗)
consumed = sold / (1 - lossRates{f});
dailyStock(f) = dailyStock(f) - consumed;
end
currentStock = dailyStock;
end
profit = revenue - totalCost;
end
function selected = selection(app, population, fitness)
% 锦标赛选择
selected = zeros(size(population));
tournamentSize = 5;
for i = 1:size(population,1)
contestants = randperm(size(population,1), tournamentSize);
[~, winnerIdx] = max(fitness(contestants));
selected(i,:) = population(contestants(winnerIdx),:);
end
end
function offspring = crossover(app, parents, crossoverProb)
offspring = parents;
n = size(parents,1);
for i = 1:2:n-1
if rand() < crossoverProb
% 算术交叉
alpha = rand();
temp1 = alpha*offspring(i,:) + (1-alpha)*offspring(i+1,:);
temp2 = alpha*offspring(i+1,:) + (1-alpha)*offspring(i,:);
offspring(i,:) = temp1;
offspring(i+1,:) = temp2;
end
end
end
function mutated = mutation(app, population, mutationProb, costs, budget, stock, storageCap)
mutated = population;
nFruits = size(population,2);
for i = 1:size(population,1)
if rand() < mutationProb
% 均匀变异
idx = randi(nFruits);
maxVal = min(floor(budget/costs(idx)), floor((storageCap - sum(stock))/nFruits));
mutated(i,idx) = randi([0, max(maxVal, 1)]);
end
end
end
function addSalesRecord(app, date, fruit, quantity)
fruitIdx = find(strcmp(app.FruitData.Name, fruit));
if isempty(fruitIdx)
error('未知水果: %s', fruit);
end
if quantity > app.FruitData.Stock(fruitIdx)
error('%s库存不足,当前库存: %.2f kg', fruit, app.FruitData.Stock(fruitIdx));
end
% 更新库存
app.FruitData.Stock(fruitIdx) = app.FruitData.Stock(fruitIdx) - quantity;
% 计算收入和利润
revenue = quantity * app.FruitData.Price(fruitIdx);
cost = quantity * app.FruitData.Cost(fruitIdx);
profit = revenue - cost;
% 添加记录
newRecord = struct('Date', date, 'Fruit', fruit, 'Quantity', quantity, ...
'Revenue', revenue, 'Cost', cost, 'Profit', profit);
app.SalesHistory = [app.SalesHistory; newRecord];
end
end
end
% 启动函数
function startFruitShopSystem()
app = FruitShopSystem();
end
最新发布