Compare commits
4 Commits
生产基础资料
...
e3b6915fbe
| Author | SHA1 | Date | |
|---|---|---|---|
| e3b6915fbe | |||
| 530a0f13e7 | |||
|
|
3bfaec8cbc | ||
|
|
1b280af9e2 |
@@ -148,3 +148,60 @@ WHERE id IN (
|
||||
'1860000000000000041',
|
||||
'1860000000000000051'
|
||||
);
|
||||
|
||||
-- ======================================================
|
||||
-- 新增菜单:原材料检验项目
|
||||
-- ======================================================
|
||||
INSERT INTO sys_permission(id, parent_id, name, url, component, component_name, menu_type, perms, perms_type, sort_no, is_route, is_leaf, hidden, status, del_flag, keep_alive, internal_or_external, create_by, create_time)
|
||||
VALUES ('1860000000000000061', '1860000000000000001', '原材料检验项目', '/mes/rawmaterialinspectitem', 'mes/material/MesRawMaterialInspectItemList', 'MesRawMaterialInspectItemList', 1, NULL, '1', 6, 1, 1, 0, '1', 0, 1, 0, 'admin', NOW())
|
||||
ON DUPLICATE KEY UPDATE
|
||||
parent_id=VALUES(parent_id), name=VALUES(name), url=VALUES(url), component=VALUES(component), component_name=VALUES(component_name),
|
||||
menu_type=VALUES(menu_type), perms=VALUES(perms), perms_type=VALUES(perms_type), sort_no=VALUES(sort_no),
|
||||
is_route=VALUES(is_route), is_leaf=VALUES(is_leaf), hidden=VALUES(hidden), status=VALUES(status), del_flag=VALUES(del_flag),
|
||||
keep_alive=VALUES(keep_alive), internal_or_external=VALUES(internal_or_external);
|
||||
INSERT INTO sys_permission(id,parent_id,name,menu_type,perms,perms_type,status,del_flag,create_by,create_time) VALUES
|
||||
('1860000000000000062','1860000000000000061','新增',2,'mes:mes_raw_material_inspect_item:add','1','1',0,'admin',NOW()),
|
||||
('1860000000000000063','1860000000000000061','编辑',2,'mes:mes_raw_material_inspect_item:edit','1','1',0,'admin',NOW()),
|
||||
('1860000000000000064','1860000000000000061','删除',2,'mes:mes_raw_material_inspect_item:delete','1','1',0,'admin',NOW()),
|
||||
('1860000000000000065','1860000000000000061','批量删除',2,'mes:mes_raw_material_inspect_item:deleteBatch','1','1',0,'admin',NOW()),
|
||||
('1860000000000000066','1860000000000000061','导出',2,'mes:mes_raw_material_inspect_item:exportXls','1','1',0,'admin',NOW()),
|
||||
('1860000000000000067','1860000000000000061','导入',2,'mes:mes_raw_material_inspect_item:importExcel','1','1',0,'admin',NOW())
|
||||
ON DUPLICATE KEY UPDATE
|
||||
parent_id=VALUES(parent_id), name=VALUES(name), menu_type=VALUES(menu_type), perms=VALUES(perms), perms_type=VALUES(perms_type),
|
||||
status=VALUES(status), del_flag=VALUES(del_flag);
|
||||
|
||||
-- ======================================================
|
||||
-- 新增菜单:原材料检验标准
|
||||
-- ======================================================
|
||||
INSERT INTO sys_permission(id, parent_id, name, url, component, component_name, menu_type, perms, perms_type, sort_no, is_route, is_leaf, hidden, status, del_flag, keep_alive, internal_or_external, create_by, create_time)
|
||||
VALUES ('1860000000000000071', '1860000000000000001', '原材料检验标准', '/mes/rawmaterialinspectstd', 'mes/material/MesRawMaterialInspectStdList', 'MesRawMaterialInspectStdList', 1, NULL, '1', 7, 1, 1, 0, '1', 0, 1, 0, 'admin', NOW())
|
||||
ON DUPLICATE KEY UPDATE
|
||||
parent_id=VALUES(parent_id), name=VALUES(name), url=VALUES(url), component=VALUES(component), component_name=VALUES(component_name),
|
||||
menu_type=VALUES(menu_type), perms=VALUES(perms), perms_type=VALUES(perms_type), sort_no=VALUES(sort_no),
|
||||
is_route=VALUES(is_route), is_leaf=VALUES(is_leaf), hidden=VALUES(hidden), status=VALUES(status), del_flag=VALUES(del_flag),
|
||||
keep_alive=VALUES(keep_alive), internal_or_external=VALUES(internal_or_external);
|
||||
INSERT INTO sys_permission(id,parent_id,name,menu_type,perms,perms_type,status,del_flag,create_by,create_time) VALUES
|
||||
('1860000000000000072','1860000000000000071','新增',2,'mes:mes_raw_material_inspect_std:add','1','1',0,'admin',NOW()),
|
||||
('1860000000000000073','1860000000000000071','编辑',2,'mes:mes_raw_material_inspect_std:edit','1','1',0,'admin',NOW()),
|
||||
('1860000000000000074','1860000000000000071','删除',2,'mes:mes_raw_material_inspect_std:delete','1','1',0,'admin',NOW()),
|
||||
('1860000000000000075','1860000000000000071','批量删除',2,'mes:mes_raw_material_inspect_std:deleteBatch','1','1',0,'admin',NOW()),
|
||||
('1860000000000000076','1860000000000000071','启用停用',2,'mes:mes_raw_material_inspect_std:enable','1','1',0,'admin',NOW()),
|
||||
('1860000000000000077','1860000000000000071','导出',2,'mes:mes_raw_material_inspect_std:exportXls','1','1',0,'admin',NOW()),
|
||||
('1860000000000000078','1860000000000000071','导入',2,'mes:mes_raw_material_inspect_std:importExcel','1','1',0,'admin',NOW())
|
||||
ON DUPLICATE KEY UPDATE
|
||||
parent_id=VALUES(parent_id), name=VALUES(name), menu_type=VALUES(menu_type), perms=VALUES(perms), perms_type=VALUES(perms_type),
|
||||
status=VALUES(status), del_flag=VALUES(del_flag);
|
||||
|
||||
-- 新增菜单授权:admin
|
||||
INSERT INTO sys_role_permission(id, role_id, permission_id, operate_date, operate_ip)
|
||||
SELECT REPLACE(UUID(),'-',''), 'f6817f48af4fb3af11b9e8bf182f618b', p.id, NOW(), '127.0.0.1'
|
||||
FROM sys_permission p
|
||||
WHERE p.id IN (
|
||||
'1860000000000000061','1860000000000000062','1860000000000000063','1860000000000000064','1860000000000000065','1860000000000000066','1860000000000000067',
|
||||
'1860000000000000071','1860000000000000072','1860000000000000073','1860000000000000074','1860000000000000075','1860000000000000076','1860000000000000077','1860000000000000078'
|
||||
) AND NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM sys_role_permission rp
|
||||
WHERE rp.role_id = 'f6817f48af4fb3af11b9e8bf182f618b'
|
||||
AND rp.permission_id = p.id
|
||||
);
|
||||
|
||||
37
jeecg-boot/db/mes-xsl-raw-material-summary-menu.sql
Normal file
37
jeecg-boot/db/mes-xsl-raw-material-summary-menu.sql
Normal file
@@ -0,0 +1,37 @@
|
||||
-- 原材料汇总页面菜单(参考:原材料库存菜单配置)
|
||||
SET NAMES utf8mb4;
|
||||
|
||||
-- 优先复用「原材料库存」的父级菜单;不存在时回退到原固定父级
|
||||
SET @xsl_parent_id = (
|
||||
SELECT parent_id
|
||||
FROM sys_permission
|
||||
WHERE id = '1900000000000000600'
|
||||
LIMIT 1
|
||||
);
|
||||
SET @xsl_parent_id = IFNULL(@xsl_parent_id, '1900000000000000300');
|
||||
|
||||
-- 页面菜单
|
||||
INSERT INTO `sys_permission` (`id`, `parent_id`, `name`, `url`, `component`, `is_route`, `component_name`, `redirect`, `menu_type`, `perms`, `perms_type`, `sort_no`, `always_show`, `icon`, `is_leaf`, `keep_alive`, `hidden`, `hide_tab`, `description`, `create_by`, `create_time`, `update_by`, `update_time`, `del_flag`, `rule_flag`, `status`, `internal_or_external`)
|
||||
SELECT '1900000000000000610', @xsl_parent_id, '原材料汇总', '/xslmes/mesXslRawMaterialSummary', 'xslmes/mesXslRawMaterialSummary/MesXslRawMaterialSummaryList', 1, NULL, NULL, 1, NULL, '0', 13.00, 0, 'ant-design:database-outlined', 0, 1, 0, 0, '原材料汇总', 'admin', NOW(), 'admin', NOW(), 0, 0, '1', 0
|
||||
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `sys_permission` WHERE `id` = '1900000000000000610');
|
||||
|
||||
-- 导出按钮权限
|
||||
INSERT INTO `sys_permission` (`id`, `parent_id`, `name`, `url`, `component`, `is_route`, `component_name`, `redirect`, `menu_type`, `perms`, `perms_type`, `sort_no`, `always_show`, `icon`, `is_leaf`, `keep_alive`, `hidden`, `hide_tab`, `description`, `create_by`, `create_time`, `update_by`, `update_time`, `del_flag`, `rule_flag`, `status`, `internal_or_external`)
|
||||
SELECT '1900000000000000611', '1900000000000000610', '导出', NULL, NULL, 0, NULL, NULL, 2, 'xslmes:mes_xsl_raw_material_summary:exportXls', '1', 1.00, 0, NULL, 1, 0, 0, 0, NULL, 'admin', NOW(), 'admin', NOW(), 0, 0, '1', 0
|
||||
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `sys_permission` WHERE `id` = '1900000000000000611');
|
||||
|
||||
-- 默认管理员角色授权
|
||||
INSERT INTO `sys_role_permission` (`id`, `role_id`, `permission_id`, `data_rule_ids`, `operate_date`, `operate_ip`)
|
||||
SELECT REPLACE(UUID(), '-', ''), 'f6817f48af4fb3af11b9e8bf182f618b', '1900000000000000610', NULL, NOW(), '127.0.0.1'
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM `sys_role_permission`
|
||||
WHERE `role_id` = 'f6817f48af4fb3af11b9e8bf182f618b' AND `permission_id` = '1900000000000000610'
|
||||
);
|
||||
|
||||
INSERT INTO `sys_role_permission` (`id`, `role_id`, `permission_id`, `data_rule_ids`, `operate_date`, `operate_ip`)
|
||||
SELECT REPLACE(UUID(), '-', ''), 'f6817f48af4fb3af11b9e8bf182f618b', '1900000000000000611', NULL, NOW(), '127.0.0.1'
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM `sys_role_permission`
|
||||
WHERE `role_id` = 'f6817f48af4fb3af11b9e8bf182f618b' AND `permission_id` = '1900000000000000611'
|
||||
);
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
package org.jeecg.modules.xslmes.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.shiro.SecurityUtils;
|
||||
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
||||
import org.jeecg.common.api.vo.Result;
|
||||
import org.jeecg.common.system.vo.LoginUser;
|
||||
import org.jeecg.common.util.oConvertUtils;
|
||||
import org.jeecg.modules.xslmes.entity.MesXslRawMaterialSummary;
|
||||
import org.jeecgframework.poi.excel.def.NormalExcelConstants;
|
||||
import org.jeecgframework.poi.excel.entity.ExportParams;
|
||||
import org.jeecgframework.poi.excel.entity.enmus.ExcelType;
|
||||
import org.jeecgframework.poi.excel.view.JeecgEntityExcelView;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
|
||||
/**
|
||||
* 原材料汇总
|
||||
*/
|
||||
@Tag(name = "原材料汇总")
|
||||
@RestController
|
||||
@RequestMapping("/xslmes/mesXslRawMaterialSummary")
|
||||
@Slf4j
|
||||
public class MesXslRawMaterialSummaryController {
|
||||
|
||||
@Autowired
|
||||
private JdbcTemplate jdbcTemplate;
|
||||
|
||||
@Operation(summary = "原材料汇总-分页列表查询")
|
||||
@GetMapping(value = "/list")
|
||||
public Result<IPage<MesXslRawMaterialSummary>> queryPageList(
|
||||
MesXslRawMaterialSummary query,
|
||||
@RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
|
||||
@RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize) {
|
||||
List<Object> params = new ArrayList<>();
|
||||
String groupedSql = buildGroupedSql(query, params);
|
||||
String countSql = "SELECT COUNT(1) FROM (" + groupedSql + ") t";
|
||||
Long total = jdbcTemplate.queryForObject(countSql, Long.class, params.toArray());
|
||||
long totalCount = total == null ? 0L : total;
|
||||
|
||||
int offset = Math.max((pageNo - 1) * pageSize, 0);
|
||||
String pageSql = groupedSql + " ORDER BY warehouseName, materialName LIMIT ? OFFSET ?";
|
||||
List<Object> pageParams = new ArrayList<>(params);
|
||||
pageParams.add(pageSize);
|
||||
pageParams.add(offset);
|
||||
List<MesXslRawMaterialSummary> rows = queryRows(pageSql, pageParams);
|
||||
|
||||
Page<MesXslRawMaterialSummary> page = new Page<>(pageNo, pageSize, totalCount);
|
||||
page.setRecords(rows);
|
||||
return Result.OK(page);
|
||||
}
|
||||
|
||||
@RequiresPermissions("xslmes:mes_xsl_raw_material_summary:exportXls")
|
||||
@RequestMapping(value = "/exportXls")
|
||||
public ModelAndView exportXls(HttpServletRequest request, MesXslRawMaterialSummary query) {
|
||||
List<Object> params = new ArrayList<>();
|
||||
String groupedSql = buildGroupedSql(query, params) + " ORDER BY warehouseName, materialName";
|
||||
List<MesXslRawMaterialSummary> exportList = queryRows(groupedSql, params);
|
||||
|
||||
LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
|
||||
ModelAndView mv = new ModelAndView(new JeecgEntityExcelView());
|
||||
mv.addObject(NormalExcelConstants.FILE_NAME, "原材料汇总");
|
||||
mv.addObject(NormalExcelConstants.CLASS, MesXslRawMaterialSummary.class);
|
||||
mv.addObject(
|
||||
NormalExcelConstants.PARAMS,
|
||||
new ExportParams("原材料汇总报表", "导出人:" + (sysUser == null ? "admin" : sysUser.getRealname()), "原材料汇总", ExcelType.XSSF));
|
||||
mv.addObject(NormalExcelConstants.DATA_LIST, exportList);
|
||||
String exportFields = request.getParameter(NormalExcelConstants.EXPORT_FIELDS);
|
||||
if (oConvertUtils.isNotEmpty(exportFields)) {
|
||||
mv.addObject(NormalExcelConstants.EXPORT_FIELDS, exportFields);
|
||||
}
|
||||
return mv;
|
||||
}
|
||||
|
||||
private String buildGroupedSql(MesXslRawMaterialSummary query, List<Object> params) {
|
||||
String warehouseExpr = "COALESCE(NULLIF(TRIM(i.warehouse_name),''), TRIM(w.warehouse_name))";
|
||||
String materialNameExpr = "COALESCE(NULLIF(TRIM(i.material_name),''), TRIM(m.material_name))";
|
||||
String materialCodeExpr = "COALESCE(NULLIF(TRIM(i.material_code),''), TRIM(m.material_code))";
|
||||
StringBuilder sql = new StringBuilder();
|
||||
sql.append("SELECT ")
|
||||
.append(warehouseExpr).append(" AS warehouseName, ")
|
||||
.append(materialNameExpr).append(" AS materialName, ")
|
||||
.append(materialCodeExpr).append(" AS materialCode, ")
|
||||
.append("SUM(COALESCE(i.total_packages,0)) AS totalPackages, ")
|
||||
.append("SUM(COALESCE(i.total_weight,0)) AS totalWeight ")
|
||||
.append("FROM mes_xsl_raw_material_inventory i ")
|
||||
.append("LEFT JOIN mes_xsl_warehouse w ON w.id = i.warehouse_id ")
|
||||
.append("AND (w.del_flag = 0 OR w.del_flag IS NULL) ")
|
||||
.append("LEFT JOIN mes_mixer_material m ON m.id = i.material_id ")
|
||||
.append("AND (m.del_flag = 0 OR m.del_flag IS NULL) ")
|
||||
.append("WHERE (i.del_flag = 0 OR i.del_flag IS NULL) ");
|
||||
|
||||
if (query != null && StringUtils.isNotBlank(query.getWarehouseName())) {
|
||||
sql.append("AND ").append(warehouseExpr).append(" LIKE ? ");
|
||||
params.add("%" + query.getWarehouseName().trim() + "%");
|
||||
}
|
||||
if (query != null && StringUtils.isNotBlank(query.getMaterialName())) {
|
||||
sql.append("AND ").append(materialNameExpr).append(" LIKE ? ");
|
||||
params.add("%" + query.getMaterialName().trim() + "%");
|
||||
}
|
||||
if (query != null && StringUtils.isNotBlank(query.getMaterialCode())) {
|
||||
sql.append("AND ").append(materialCodeExpr).append(" LIKE ? ");
|
||||
params.add("%" + query.getMaterialCode().trim() + "%");
|
||||
}
|
||||
|
||||
sql.append("GROUP BY ")
|
||||
.append(warehouseExpr).append(", ")
|
||||
.append(materialNameExpr).append(", ")
|
||||
.append(materialCodeExpr);
|
||||
return sql.toString();
|
||||
}
|
||||
|
||||
private List<MesXslRawMaterialSummary> queryRows(String sql, List<Object> params) {
|
||||
return jdbcTemplate.query(
|
||||
sql,
|
||||
rs -> {
|
||||
List<MesXslRawMaterialSummary> list = new ArrayList<>();
|
||||
int seq = 1;
|
||||
while (rs.next()) {
|
||||
MesXslRawMaterialSummary row = new MesXslRawMaterialSummary();
|
||||
row.setWarehouseName(trim(rs.getString("warehouseName")));
|
||||
row.setMaterialName(trim(rs.getString("materialName")));
|
||||
row.setMaterialCode(trim(rs.getString("materialCode")));
|
||||
row.setTotalPackages(rs.getBigDecimal("totalPackages"));
|
||||
row.setTotalWeight(rs.getBigDecimal("totalWeight"));
|
||||
row.setId(row.getWarehouseName() + "_" + row.getMaterialCode() + "_" + seq++);
|
||||
list.add(row);
|
||||
}
|
||||
return list;
|
||||
},
|
||||
params.toArray());
|
||||
}
|
||||
|
||||
private String trim(String v) {
|
||||
return v == null ? "" : v.trim();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package org.jeecg.modules.xslmes.entity;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import lombok.Data;
|
||||
import org.jeecgframework.poi.excel.annotation.Excel;
|
||||
|
||||
/**
|
||||
* 原材料汇总(按仓库+物料)
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "原材料汇总")
|
||||
public class MesXslRawMaterialSummary implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "主键")
|
||||
private String id;
|
||||
|
||||
@Excel(name = "所在仓库", width = 20)
|
||||
@Schema(description = "所在仓库")
|
||||
private String warehouseName;
|
||||
|
||||
@Excel(name = "物料名称", width = 20)
|
||||
@Schema(description = "物料名称")
|
||||
private String materialName;
|
||||
|
||||
@Excel(name = "物料编码", width = 20)
|
||||
@Schema(description = "物料编码")
|
||||
private String materialCode;
|
||||
|
||||
@Excel(name = "总包数", width = 15)
|
||||
@Schema(description = "总包数")
|
||||
private BigDecimal totalPackages;
|
||||
|
||||
@Excel(name = "总重量", width = 15)
|
||||
@Schema(description = "总重量")
|
||||
private BigDecimal totalWeight;
|
||||
}
|
||||
1340
jeecgboot-vue3/pnpm-lock.yaml
generated
1340
jeecgboot-vue3/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,10 @@
|
||||
import { defHttp } from '/@/utils/http/axios';
|
||||
|
||||
enum Api {
|
||||
list = '/xslmes/mesXslRawMaterialSummary/list',
|
||||
exportXls = '/xslmes/mesXslRawMaterialSummary/exportXls',
|
||||
}
|
||||
|
||||
export const getExportUrl = Api.exportXls;
|
||||
|
||||
export const list = (params) => defHttp.get({ url: Api.list, params });
|
||||
@@ -0,0 +1,23 @@
|
||||
import { BasicColumn, FormSchema } from '/@/components/Table';
|
||||
|
||||
export const columns: BasicColumn[] = [
|
||||
{ title: '所在仓库', align: 'center', dataIndex: 'warehouseName', width: 180 },
|
||||
{ title: '物料名称', align: 'center', dataIndex: 'materialName', width: 180, ellipsis: true },
|
||||
{ title: '物料编码', align: 'center', dataIndex: 'materialCode', width: 180 },
|
||||
{ title: '总包数', align: 'center', dataIndex: 'totalPackages', width: 140 },
|
||||
{ title: '总重量', align: 'center', dataIndex: 'totalWeight', width: 140 },
|
||||
];
|
||||
|
||||
export const searchFormSchema: FormSchema[] = [
|
||||
{ label: '所在仓库', field: 'warehouseName', component: 'JInput', colProps: { span: 6 } },
|
||||
{ label: '物料名称', field: 'materialName', component: 'JInput', colProps: { span: 6 } },
|
||||
{ label: '物料编码', field: 'materialCode', component: 'JInput', colProps: { span: 6 } },
|
||||
];
|
||||
|
||||
export const superQuerySchema = {
|
||||
warehouseName: { title: '所在仓库', order: 0, view: 'text' },
|
||||
materialName: { title: '物料名称', order: 1, view: 'text' },
|
||||
materialCode: { title: '物料编码', order: 2, view: 'text' },
|
||||
totalPackages: { title: '总包数', order: 3, view: 'number' },
|
||||
totalWeight: { title: '总重量', order: 4, view: 'number' },
|
||||
};
|
||||
@@ -0,0 +1,54 @@
|
||||
<template>
|
||||
<div>
|
||||
<BasicTable @register="registerTable">
|
||||
<template #tableTitle>
|
||||
<a-button type="primary" v-auth="'xslmes:mes_xsl_raw_material_summary:exportXls'" preIcon="ant-design:export-outlined" @click="onExportXls">
|
||||
导出
|
||||
</a-button>
|
||||
<super-query :config="superQueryConfig" @search="handleSuperQuery" />
|
||||
</template>
|
||||
</BasicTable>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" name="xslmes-mesXslRawMaterialSummary" setup>
|
||||
import { reactive } from 'vue';
|
||||
import { BasicTable } from '/@/components/Table';
|
||||
import { useListPage } from '/@/hooks/system/useListPage';
|
||||
import { columns, searchFormSchema, superQuerySchema } from './MesXslRawMaterialSummary.data';
|
||||
import { list, getExportUrl } from './MesXslRawMaterialSummary.api';
|
||||
|
||||
const queryParam = reactive<any>({});
|
||||
|
||||
const { tableContext, onExportXls } = useListPage({
|
||||
tableProps: {
|
||||
title: '原材料汇总',
|
||||
api: list,
|
||||
columns,
|
||||
canResize: true,
|
||||
formConfig: {
|
||||
schemas: searchFormSchema,
|
||||
autoSubmitOnEnter: true,
|
||||
showAdvancedButton: true,
|
||||
},
|
||||
beforeFetch: (params) => {
|
||||
return Object.assign(params, queryParam);
|
||||
},
|
||||
},
|
||||
exportConfig: {
|
||||
name: '原材料汇总',
|
||||
url: getExportUrl,
|
||||
params: queryParam,
|
||||
},
|
||||
});
|
||||
|
||||
const [registerTable, { reload }] = tableContext;
|
||||
const superQueryConfig = reactive(superQuerySchema);
|
||||
|
||||
function handleSuperQuery(params) {
|
||||
Object.keys(params).forEach((k) => {
|
||||
queryParam[k] = params[k];
|
||||
});
|
||||
reload();
|
||||
}
|
||||
</script>
|
||||
BIN
yy-admin-master/YY.Admin-Release-win-x64-publish.zip
Normal file
BIN
yy-admin-master/YY.Admin-Release-win-x64-publish.zip
Normal file
Binary file not shown.
@@ -10,6 +10,7 @@ using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Linq.Dynamic.Core;
|
||||
using System.Reflection;
|
||||
@@ -19,7 +20,9 @@ using Yitter.IdGenerator;
|
||||
using YY.Admin.Core;
|
||||
using YY.Admin.Core.Extension;
|
||||
using YY.Admin.Core.Option;
|
||||
using YY.Admin.Core.SeedData;
|
||||
using YY.Admin.Core.Session;
|
||||
using YY.Admin.Core.Util;
|
||||
using DbType = SqlSugar.DbType;
|
||||
|
||||
namespace YY.Admin.Core.SqlSugar
|
||||
@@ -160,6 +163,12 @@ namespace YY.Admin.Core.SqlSugar
|
||||
config.ConnectionString = CryptogramUtil.Decrypt(config.ConnectionString);
|
||||
}
|
||||
|
||||
// SQLite 相对路径默认依赖进程工作目录;快捷方式/从不同目录启动会在别处生成空库,表现为发布后菜单全空
|
||||
if (config.DbType == DbType.Sqlite && !string.IsNullOrWhiteSpace(config.ConnectionString))
|
||||
{
|
||||
config.ConnectionString = ResolveSqliteConnectionToAbsolutePath(config.ConnectionString);
|
||||
}
|
||||
|
||||
var configureExternalServices = new ConfigureExternalServices
|
||||
{
|
||||
EntityNameService = (type, entity) => // 处理表
|
||||
@@ -250,6 +259,96 @@ namespace YY.Admin.Core.SqlSugar
|
||||
//if (config.DbSettings.EnableInitView) InitView(dbProvider);
|
||||
// 初始化种子数据
|
||||
if (config.SeedSettings.EnableInitSeed) InitSeedData(db, config);
|
||||
// 关闭全量种子时首启可能无菜单数据;补一份基准菜单,避免打包版本左侧空白
|
||||
EnsureBaselineSysMenuSeed(db, config);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将 SQLite 连接串中的相对 DataSource 解析为基于应用程序基目录的绝对路径。
|
||||
/// </summary>
|
||||
private static string ResolveSqliteConnectionToAbsolutePath(string connectionString)
|
||||
{
|
||||
try
|
||||
{
|
||||
var builder = new SqliteConnectionStringBuilder(connectionString);
|
||||
var ds = builder.DataSource;
|
||||
if (string.IsNullOrWhiteSpace(ds))
|
||||
{
|
||||
return connectionString;
|
||||
}
|
||||
|
||||
if (Path.IsPathFullyQualified(ds))
|
||||
{
|
||||
return connectionString;
|
||||
}
|
||||
|
||||
// Program Files 等安装目录对普通用户只读;SQLite 库放到 LocalApplicationData
|
||||
var dataDir = AppWritablePaths.EnsureDirectoryExists(AppWritablePaths.DataDirectory);
|
||||
var fileName = Path.GetFileName(ds.TrimStart('.', '/', '\\'));
|
||||
if (string.IsNullOrWhiteSpace(fileName))
|
||||
{
|
||||
fileName = "Admin.NET.db";
|
||||
}
|
||||
|
||||
builder.DataSource = Path.Combine(dataDir, fileName);
|
||||
return builder.ConnectionString;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return connectionString;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 未启用 EnableInitSeed 且 sys_menu 为空时,写入 SysMenu 种子,避免发布后界面无功能菜单。
|
||||
/// </summary>
|
||||
private static void EnsureBaselineSysMenuSeed(SqlSugarScope db, DbConnectionConfig config)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (config.SeedSettings.EnableInitSeed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!string.Equals(config.ConfigId.ToString(), SqlSugarConst.MainConfigId, StringComparison.Ordinal))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (config.DbType != DbType.Sqlite)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var dbProvider = db.GetConnectionScope(config.ConfigId);
|
||||
var menuEntityInfo = dbProvider.EntityMaintenance.GetEntityInfo(typeof(SysMenu));
|
||||
if (!dbProvider.DbMaintenance.IsAnyTable(menuEntityInfo.DbTableName, false))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var cnt = dbProvider.Queryable<SysMenu>().ClearFilter().Count();
|
||||
if (cnt > 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var seedType = typeof(SysMenuSeedData);
|
||||
var seedData = GetSeedData(seedType)?.ToList();
|
||||
if (seedData == null || seedData.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
AdjustSeedDataIds(seedData, config);
|
||||
var progress = 0;
|
||||
InsertOrUpdateSeedData(dbProvider, seedType, typeof(SysMenu), seedData, config, ref progress, 1);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 启动阶段不因兜底种子失败而阻断
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
38
yy-admin-master/YY.Admin.Core/Util/AppWritablePaths.cs
Normal file
38
yy-admin-master/YY.Admin.Core/Util/AppWritablePaths.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using System.IO;
|
||||
|
||||
namespace YY.Admin.Core.Util;
|
||||
|
||||
/// <summary>
|
||||
/// 当前用户可写应用数据目录(避免安装在 Program Files 时无写权限)。
|
||||
/// </summary>
|
||||
public static class AppWritablePaths
|
||||
{
|
||||
private static readonly string Root = Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
|
||||
"YY.Admin");
|
||||
|
||||
/// <summary>应用私有根目录:%LocalAppData%\YY.Admin</summary>
|
||||
public static string LocalApplicationRoot => Root;
|
||||
|
||||
/// <summary>SQLite 等业务数据库目录。</summary>
|
||||
public static string DataDirectory => Path.Combine(Root, "Data");
|
||||
|
||||
/// <summary>用户覆盖的配置、Jeecg 同步状态等。</summary>
|
||||
public static string ConfigurationDirectory => Path.Combine(Root, "Configuration");
|
||||
|
||||
/// <summary>按账号划分的本地设置(对应 CommonConst.AppSettingsFilePath 前缀)。</summary>
|
||||
public static string AccountSettingsRootDirectory => Path.Combine(Root, "AppSettings");
|
||||
|
||||
/// <summary>
|
||||
/// 创建目录(若不存在)并返回路径。
|
||||
/// </summary>
|
||||
public static string EnsureDirectoryExists(string directoryPath)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(directoryPath))
|
||||
{
|
||||
Directory.CreateDirectory(directoryPath);
|
||||
}
|
||||
|
||||
return directoryPath;
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ using System.Net.Http;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using YY.Admin.Core.Util;
|
||||
|
||||
namespace YY.Admin.Services.Service.Jeecg;
|
||||
|
||||
@@ -226,7 +227,8 @@ public class JeecgLoginLogReportService : IJeecgLoginLogReportService, IClientLo
|
||||
|
||||
private static string GetQueuePath()
|
||||
{
|
||||
return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "AppSettings", "offline-scada-log-queue.jsonl");
|
||||
var dir = AppWritablePaths.EnsureDirectoryExists(AppWritablePaths.AccountSettingsRootDirectory);
|
||||
return Path.Combine(dir, "offline-scada-log-queue.jsonl");
|
||||
}
|
||||
|
||||
private static void EnsureQueueDir(string path)
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using YY.Admin.Core.Util;
|
||||
|
||||
namespace YY.Admin.Services.Service.Jeecg
|
||||
{
|
||||
/// <summary>
|
||||
/// 读写本地 Jeecg 同步状态文件(与 appsettings 同目录下的 Configuration)
|
||||
/// 读写本地 Jeecg 同步状态文件(用户可写目录,避免 Program Files 无写权限)。
|
||||
/// </summary>
|
||||
public class JeecgSyncStateStore
|
||||
{
|
||||
@@ -12,7 +13,7 @@ namespace YY.Admin.Services.Service.Jeecg
|
||||
|
||||
public JeecgSyncStateStore()
|
||||
{
|
||||
var dir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Configuration");
|
||||
var dir = AppWritablePaths.EnsureDirectoryExists(AppWritablePaths.ConfigurationDirectory);
|
||||
_filePath = Path.Combine(dir, "jeecg-sync-state.json");
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,9 @@ using Mapster;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using NewLife;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Windows;
|
||||
using System.Windows.Threading;
|
||||
using YY.Admin.Core;
|
||||
using YY.Admin.EventBus;
|
||||
using YY.Admin.Filter;
|
||||
@@ -14,6 +16,7 @@ using YY.Admin.Services.Service.Print;
|
||||
using YY.Admin.Setup;
|
||||
using YY.Admin.ViewModels;
|
||||
using YY.Admin.Views;
|
||||
using YY.Admin.Core.Util;
|
||||
namespace YY.Admin
|
||||
{
|
||||
/// <summary>
|
||||
@@ -24,32 +27,116 @@ namespace YY.Admin
|
||||
private IConfiguration? _configuration;
|
||||
private ILoggerService? _logger;
|
||||
private readonly SyncModule _syncModule = new();
|
||||
|
||||
public App()
|
||||
{
|
||||
DispatcherUnhandledException += OnDispatcherUnhandledException;
|
||||
AppDomain.CurrentDomain.UnhandledException += OnUnhandledDomainException;
|
||||
}
|
||||
|
||||
private static void TryWriteCrashLog(string headline, Exception ex)
|
||||
{
|
||||
try
|
||||
{
|
||||
var dir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "YY.Admin", "logs");
|
||||
Directory.CreateDirectory(dir);
|
||||
var file = Path.Combine(dir, $"crash-{DateTime.Now:yyyyMMdd-HHmmss}.txt");
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine(headline);
|
||||
sb.AppendLine(ex.ToString());
|
||||
File.WriteAllText(file, sb.ToString(), Encoding.UTF8);
|
||||
MessageBox.Show($"程序异常已写入日志:\n{file}\n\n{ex.Message}", "智能制造MES工控", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
catch
|
||||
{
|
||||
MessageBox.Show($"{headline}\n{ex}", "智能制造MES工控", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
|
||||
{
|
||||
TryWriteCrashLog("UI 线程未处理异常", e.Exception);
|
||||
e.Handled = true;
|
||||
Shutdown(1);
|
||||
}
|
||||
|
||||
private void OnUnhandledDomainException(object? sender, UnhandledExceptionEventArgs e)
|
||||
{
|
||||
if (e.ExceptionObject is Exception ex)
|
||||
{
|
||||
TryWriteCrashLog("域未处理异常", ex);
|
||||
}
|
||||
|
||||
if (e.IsTerminating)
|
||||
{
|
||||
Environment.Exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
protected override Window CreateShell()
|
||||
{
|
||||
return Container.Resolve<LoginWindow>();
|
||||
}
|
||||
protected override void OnStartup(StartupEventArgs e)
|
||||
{
|
||||
var baseDirectory = AppDomain.CurrentDomain.BaseDirectory;
|
||||
// 构建配置
|
||||
_configuration = new ConfigurationBuilder()
|
||||
.SetBasePath(baseDirectory)
|
||||
.AddJsonFile("Configuration/appsettings.json", optional: false, reloadOnChange: true)
|
||||
.Build();
|
||||
// 全局配置
|
||||
TypeAdapterConfig.GlobalSettings.Default
|
||||
.IgnoreNullValues(true)
|
||||
.NameMatchingStrategy(NameMatchingStrategy.IgnoreCase);
|
||||
try
|
||||
{
|
||||
var baseDirectory = AppDomain.CurrentDomain.BaseDirectory;
|
||||
var cfgPath = Path.Combine(baseDirectory, "Configuration", "appsettings.json");
|
||||
if (!File.Exists(cfgPath))
|
||||
{
|
||||
var msg = $"未找到配置文件(请确认与 YY.Admin.exe 同目录存在 Configuration\\appsettings.json):\n{cfgPath}";
|
||||
TryWriteStartupFailure(msg);
|
||||
Shutdown(1);
|
||||
return;
|
||||
}
|
||||
|
||||
// FluentValidation 全局规则级别配置
|
||||
ValidatorOptions.Global.DefaultRuleLevelCascadeMode = CascadeMode.Stop;
|
||||
var userCfgPath = Path.Combine(
|
||||
AppWritablePaths.EnsureDirectoryExists(AppWritablePaths.ConfigurationDirectory),
|
||||
"appsettings.json");
|
||||
|
||||
// Mapster 全局配置
|
||||
#if DEBUG
|
||||
//TypeAdapterConfig.GlobalSettings.RequireExplicitMapping = true;
|
||||
#endif
|
||||
// 构建配置:安装目录默认 + 用户目录覆盖(JeecgIntegration 等可由服务器设置写入)
|
||||
_configuration = new ConfigurationBuilder()
|
||||
.SetBasePath(baseDirectory)
|
||||
.AddJsonFile(Path.Combine("Configuration", "appsettings.json"), optional: false, reloadOnChange: true)
|
||||
.AddJsonFile(userCfgPath, optional: true, reloadOnChange: true)
|
||||
.Build();
|
||||
// 全局配置
|
||||
TypeAdapterConfig.GlobalSettings.Default
|
||||
.IgnoreNullValues(true)
|
||||
.NameMatchingStrategy(NameMatchingStrategy.IgnoreCase);
|
||||
|
||||
base.OnStartup(e);
|
||||
// FluentValidation 全局规则级别配置
|
||||
ValidatorOptions.Global.DefaultRuleLevelCascadeMode = CascadeMode.Stop;
|
||||
|
||||
// Mapster 全局配置
|
||||
#if DEBUG
|
||||
//TypeAdapterConfig.GlobalSettings.RequireExplicitMapping = true;
|
||||
#endif
|
||||
|
||||
base.OnStartup(e);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
TryWriteCrashLog("启动阶段异常(配置/框架初始化失败)", ex);
|
||||
Shutdown(1);
|
||||
}
|
||||
}
|
||||
|
||||
private static void TryWriteStartupFailure(string message)
|
||||
{
|
||||
try
|
||||
{
|
||||
var dir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "YY.Admin", "logs");
|
||||
Directory.CreateDirectory(dir);
|
||||
var file = Path.Combine(dir, $"startup-{DateTime.Now:yyyyMMdd-HHmmss}.txt");
|
||||
File.WriteAllText(file, message, Encoding.UTF8);
|
||||
MessageBox.Show($"{message}\n\n已记录:{file}", "智能制造MES工控", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
catch
|
||||
{
|
||||
MessageBox.Show(message, "智能制造MES工控", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
//注册
|
||||
protected override void RegisterTypes(IContainerRegistry containerRegistry)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System.IO;
|
||||
using YY.Admin.Core.Util;
|
||||
|
||||
namespace YY.Admin.Helper
|
||||
{
|
||||
@@ -11,6 +12,37 @@ namespace YY.Admin.Helper
|
||||
{
|
||||
private const string DefaultWebSocketPath = "/websocket/scada-sync";
|
||||
|
||||
/// <summary>
|
||||
/// 安装目录随包发布的默认配置(只读)。
|
||||
/// </summary>
|
||||
public static string GetBundledAppSettingsPath()
|
||||
{
|
||||
return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Configuration", "appsettings.json");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 用户覆盖配置(可写),仅覆盖 JeecgIntegration 节点时使用。
|
||||
/// </summary>
|
||||
public static string GetUserAppSettingsPath()
|
||||
{
|
||||
var dir = AppWritablePaths.EnsureDirectoryExists(AppWritablePaths.ConfigurationDirectory);
|
||||
return Path.Combine(dir, "appsettings.json");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 兼容旧调用:优先返回用于读取的实际路径(存在用户覆盖则用用户文件)。
|
||||
/// </summary>
|
||||
public static string GetConfigPath()
|
||||
{
|
||||
var user = GetUserAppSettingsPath();
|
||||
if (File.Exists(user))
|
||||
{
|
||||
return user;
|
||||
}
|
||||
|
||||
return GetBundledAppSettingsPath();
|
||||
}
|
||||
|
||||
public class ServerSettingsModel
|
||||
{
|
||||
public string Ip { get; set; } = "127.0.0.1";
|
||||
@@ -25,22 +57,35 @@ namespace YY.Admin.Helper
|
||||
public bool DisconnectConnection { get; set; } = false;
|
||||
}
|
||||
|
||||
public static string GetConfigPath()
|
||||
private static JObject LoadMergedRoot()
|
||||
{
|
||||
return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Configuration", "appsettings.json");
|
||||
var bundledPath = GetBundledAppSettingsPath();
|
||||
if (!File.Exists(bundledPath))
|
||||
{
|
||||
throw new FileNotFoundException("未找到安装目录默认配置文件 appsettings.json", bundledPath);
|
||||
}
|
||||
|
||||
var root = JObject.Parse(File.ReadAllText(bundledPath));
|
||||
var userPath = GetUserAppSettingsPath();
|
||||
if (!File.Exists(userPath))
|
||||
{
|
||||
return root;
|
||||
}
|
||||
|
||||
var userRoot = JObject.Parse(File.ReadAllText(userPath));
|
||||
var userJeecg = userRoot["JeecgIntegration"] as JObject;
|
||||
if (userJeecg != null)
|
||||
{
|
||||
root["JeecgIntegration"] = userJeecg;
|
||||
}
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
public static ServerSettingsModel Load()
|
||||
{
|
||||
var model = new ServerSettingsModel();
|
||||
var path = GetConfigPath();
|
||||
if (!File.Exists(path))
|
||||
{
|
||||
return model;
|
||||
}
|
||||
|
||||
var content = File.ReadAllText(path);
|
||||
var root = JObject.Parse(content);
|
||||
var root = LoadMergedRoot();
|
||||
var jeecg = root["JeecgIntegration"] as JObject;
|
||||
if (jeecg == null)
|
||||
{
|
||||
@@ -64,14 +109,7 @@ namespace YY.Admin.Helper
|
||||
|
||||
public static void Save(ServerSettingsModel model)
|
||||
{
|
||||
var path = GetConfigPath();
|
||||
if (!File.Exists(path))
|
||||
{
|
||||
throw new FileNotFoundException("未找到配置文件 appsettings.json", path);
|
||||
}
|
||||
|
||||
var content = File.ReadAllText(path);
|
||||
var root = JObject.Parse(content);
|
||||
var root = LoadMergedRoot();
|
||||
var jeecg = root["JeecgIntegration"] as JObject;
|
||||
if (jeecg == null)
|
||||
{
|
||||
@@ -95,7 +133,9 @@ namespace YY.Admin.Helper
|
||||
jeecg["WebSocketPath"] = webSocketPath;
|
||||
jeecg["DisconnectConnection"] = model.DisconnectConnection;
|
||||
|
||||
File.WriteAllText(path, root.ToString(Formatting.Indented));
|
||||
var userPath = GetUserAppSettingsPath();
|
||||
var outRoot = new JObject { ["JeecgIntegration"] = jeecg };
|
||||
File.WriteAllText(userPath, outRoot.ToString(Formatting.Indented));
|
||||
}
|
||||
|
||||
public static string BuildDefaultWebSocketUrl(string baseScheme, string ip, int port, string basePath, string webSocketPath = DefaultWebSocketPath)
|
||||
@@ -106,6 +146,7 @@ namespace YY.Admin.Helper
|
||||
{
|
||||
safeBasePath = "/" + safeBasePath;
|
||||
}
|
||||
|
||||
var safeWsPath = NormalizeWebSocketPath(webSocketPath);
|
||||
return $"{safeScheme}://{ip}:{port}{safeBasePath}{safeWsPath}";
|
||||
}
|
||||
@@ -117,6 +158,7 @@ namespace YY.Admin.Helper
|
||||
{
|
||||
value = "/" + value;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
23
yy-admin-master/YY.Admin/Helper/WebView2UserDataFolder.cs
Normal file
23
yy-admin-master/YY.Admin/Helper/WebView2UserDataFolder.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using System.IO;
|
||||
using Microsoft.Web.WebView2.Wpf;
|
||||
using YY.Admin.Core.Util;
|
||||
|
||||
namespace YY.Admin.Helper;
|
||||
|
||||
/// <summary>
|
||||
/// WebView2 默认将用户数据目录放在宿主 exe 旁边;安装在 Program Files 时目录只读会导致初始化失败、预览白屏。
|
||||
/// 统一到当前用户 LocalAppData 下的可写路径。
|
||||
/// </summary>
|
||||
public static class WebView2UserDataFolder
|
||||
{
|
||||
/// <summary>
|
||||
/// 为控件创建 CreationProperties(须在首次 EnsureCoreWebView2Async 之前赋值)。
|
||||
/// </summary>
|
||||
/// <param name="subFolder">子目录名,避免不同场景争用同一 profile。</param>
|
||||
public static CoreWebView2CreationProperties CreateCreationProperties(string subFolder)
|
||||
{
|
||||
var folder = AppWritablePaths.EnsureDirectoryExists(
|
||||
Path.Combine(AppWritablePaths.LocalApplicationRoot, "WebView2", subFolder));
|
||||
return new CoreWebView2CreationProperties { UserDataFolder = folder };
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ using System.Text;
|
||||
using System.Windows;
|
||||
using Microsoft.Web.WebView2.Core;
|
||||
using Microsoft.Web.WebView2.Wpf;
|
||||
using YY.Admin.Core.Util;
|
||||
|
||||
namespace YY.Admin.Infrastructure.Print;
|
||||
|
||||
@@ -42,7 +43,9 @@ public static class HtmlToPdfRenderer
|
||||
{
|
||||
File.WriteAllText(tempHtml, html, Encoding.UTF8);
|
||||
|
||||
var env = await CoreWebView2Environment.CreateAsync();
|
||||
var userData = AppWritablePaths.EnsureDirectoryExists(
|
||||
Path.Combine(AppWritablePaths.LocalApplicationRoot, "WebView2", "HtmlToPdf"));
|
||||
var env = await CoreWebView2Environment.CreateAsync(browserExecutableFolder: null, userDataFolder: userData);
|
||||
var wv = new WebView2();
|
||||
|
||||
win = new Window
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
using Prism.Dialogs;
|
||||
using YY.Admin.ViewModels.Control;
|
||||
using YY.Admin.ViewModels.Dialogs;
|
||||
using YY.Admin.Views;
|
||||
@@ -20,6 +20,15 @@ using YY.Admin.Views.Print;
|
||||
|
||||
namespace YY.Admin
|
||||
{
|
||||
/// <summary>
|
||||
/// Prism DialogService 中 <see cref="KnownDialogParameters.WindowName"/> 使用的宿主窗口注册名。
|
||||
/// </summary>
|
||||
public static class DialogWindowNames
|
||||
{
|
||||
/// <summary>标准边框、可调整大小,且不使用 AllowsTransparency(服务器设置等需改 WindowStyle 的对话框)。</summary>
|
||||
public const string ChromeDialogWindow = "ChromeDialogWindow";
|
||||
}
|
||||
|
||||
public static class NavigationExtensions
|
||||
{
|
||||
/// <summary>
|
||||
@@ -35,8 +44,9 @@ namespace YY.Admin
|
||||
containerRegistry.RegisterDialog<ConfirmDialogView, ConfirmDialogViewModel>("ConfirmDialog");
|
||||
containerRegistry.RegisterDialog<ServerSettingsDialogView, ServerSettingsDialogViewModel>("ServerSettingsDialog");
|
||||
|
||||
// 设置对话框样式
|
||||
// 默认透明无边框宿主;需调整 WindowStyle/AllowsTransparency 的对话框改用命名宿主 ChromeDialogWindow
|
||||
containerRegistry.RegisterDialogWindow<DialogWindow>();
|
||||
containerRegistry.RegisterDialogWindow<ChromeDialogWindow>(DialogWindowNames.ChromeDialogWindow);
|
||||
|
||||
// 注册导航
|
||||
containerRegistry.RegisterForNavigation<DashboardView>("DashboardView");
|
||||
@@ -103,6 +113,30 @@ namespace YY.Admin
|
||||
}
|
||||
public IDialogResult? Result { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 标准窗口边框宿主:从构造起即 AllowsTransparency=false,避免窗口显示后再切换透明属性引发异常。
|
||||
/// </summary>
|
||||
public class ChromeDialogWindow : Window, IDialogWindow
|
||||
{
|
||||
public ChromeDialogWindow()
|
||||
{
|
||||
AllowsTransparency = false;
|
||||
Background = SystemColors.WindowBrush;
|
||||
WindowStyle = WindowStyle.SingleBorderWindow;
|
||||
ResizeMode = ResizeMode.CanResizeWithGrip;
|
||||
WindowStartupLocation = WindowStartupLocation.CenterOwner;
|
||||
// 按内容测量客户区高度;勿将 Window.Height 设为与 UserControl 相同数值,
|
||||
// 否则会与标题栏/边框抢高度导致底部按钮被裁切。
|
||||
SizeToContent = SizeToContent.WidthAndHeight;
|
||||
MinWidth = 520;
|
||||
MinHeight = 360;
|
||||
Title = "对话框";
|
||||
}
|
||||
|
||||
public IDialogResult? Result { get; set; }
|
||||
}
|
||||
|
||||
//public class DialogWindow : Window, IDialogWindow
|
||||
//{
|
||||
// public DialogWindow()
|
||||
|
||||
@@ -11,6 +11,7 @@ using YY.Admin.Core.EventBus;
|
||||
using YY.Admin.Core.Helper;
|
||||
using YY.Admin.Core.Model;
|
||||
using YY.Admin.Core.Session;
|
||||
using YY.Admin.Core.Util;
|
||||
using HcSkinType = HandyControl.Data.SkinType;
|
||||
|
||||
namespace YY.Admin.ViewModels
|
||||
@@ -167,7 +168,10 @@ namespace YY.Admin.ViewModels
|
||||
public static string GetFilePath()
|
||||
{
|
||||
string filePathSuffix = string.Format(CommonConst.AppSettingsFilePath, AppSession.CurrentUser!.Account);
|
||||
return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, filePathSuffix);
|
||||
var fullPath = Path.Combine(AppWritablePaths.LocalApplicationRoot, filePathSuffix);
|
||||
var dir = Path.GetDirectoryName(fullPath);
|
||||
AppWritablePaths.EnsureDirectoryExists(dir!);
|
||||
return fullPath;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -2,6 +2,8 @@ using System.Windows;
|
||||
using System.Windows.Media;
|
||||
using System.Net.Http;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Prism.Dialogs;
|
||||
using YY.Admin;
|
||||
using YY.Admin.Core.Helper;
|
||||
using YY.Admin.Core.Session;
|
||||
using YY.Admin.FluentValidation;
|
||||
@@ -291,7 +293,11 @@ namespace YY.Admin.ViewModels
|
||||
|
||||
private void OpenServerSettings()
|
||||
{
|
||||
_dialogService.ShowDialog("ServerSettingsDialog", r =>
|
||||
var parameters = new DialogParameters
|
||||
{
|
||||
{ KnownDialogParameters.WindowName, DialogWindowNames.ChromeDialogWindow },
|
||||
};
|
||||
_dialogService.ShowDialog("ServerSettingsDialog", parameters, r =>
|
||||
{
|
||||
if (r.Result == ButtonResult.OK)
|
||||
{
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Mapster;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Prism.Dialogs;
|
||||
using Newtonsoft.Json;
|
||||
using SqlSugar;
|
||||
using System.Collections.ObjectModel;
|
||||
@@ -9,6 +10,7 @@ using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Threading;
|
||||
using YY.Admin;
|
||||
using YY.Admin.Core;
|
||||
using YY.Admin.Core.Const;
|
||||
using YY.Admin.Core.Model;
|
||||
@@ -16,12 +18,12 @@ using YY.Admin.Core.Services;
|
||||
using YY.Admin.Core.Session;
|
||||
using YY.Admin.Core.Util;
|
||||
using YY.Admin.Event;
|
||||
using YY.Admin.Helper;
|
||||
using YY.Admin.Module;
|
||||
using YY.Admin.Services.Service.Auth;
|
||||
using YY.Admin.Services.Service.Jeecg;
|
||||
using YY.Admin.Services.Service.Menu;
|
||||
using YY.Admin.ViewModels.Control;
|
||||
using YY.Admin.Helper;
|
||||
|
||||
namespace YY.Admin.ViewModels
|
||||
{
|
||||
@@ -724,7 +726,11 @@ namespace YY.Admin.ViewModels
|
||||
|
||||
private void OpenServerSettings()
|
||||
{
|
||||
_dialogService.ShowDialog("ServerSettingsDialog", r =>
|
||||
var parameters = new DialogParameters
|
||||
{
|
||||
{ KnownDialogParameters.WindowName, DialogWindowNames.ChromeDialogWindow },
|
||||
};
|
||||
_dialogService.ShowDialog("ServerSettingsDialog", parameters, r =>
|
||||
{
|
||||
if (r.Result == ButtonResult.OK)
|
||||
{
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:ctls="clr-namespace:YY.Admin.Core.Controls;assembly=YY.Admin.Core"
|
||||
Width="520" Height="420"
|
||||
Loaded="UserControl_Loaded">
|
||||
MinWidth="520">
|
||||
<Border Background="White" BorderBrush="#f0f0f0" BorderThickness="1" CornerRadius="4">
|
||||
<Grid Margin="0">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<!-- Auto:按表单实际高度排列,避免固定窗体高度时中间行被压扁裁切 -->
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System.Windows.Controls;
|
||||
using System.Windows;
|
||||
|
||||
namespace YY.Admin.Views.Dialogs
|
||||
{
|
||||
@@ -12,22 +11,5 @@ namespace YY.Admin.Views.Dialogs
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void UserControl_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var win = Window.GetWindow(this);
|
||||
if (win == null) return;
|
||||
|
||||
// 必须先关闭 AllowsTransparency,再改 WindowStyle(否则会抛出:
|
||||
// 「当 AllowsTransparency 为 true 时,WindowStyle.None 是唯一有效值」)
|
||||
win.AllowsTransparency = false;
|
||||
win.WindowStyle = WindowStyle.SingleBorderWindow;
|
||||
win.ResizeMode = ResizeMode.CanResizeWithGrip;
|
||||
win.SizeToContent = SizeToContent.Manual;
|
||||
win.MinWidth = 520;
|
||||
win.MinHeight = 420;
|
||||
if (win.Width < 520) win.Width = 520;
|
||||
if (win.Height < 420) win.Height = 420;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ using System.Text.RegularExpressions;
|
||||
using System.Windows;
|
||||
using YY.Admin.Core.Entity;
|
||||
using YY.Admin.Core.Services;
|
||||
using YY.Admin.Helper;
|
||||
using YY.Admin.Services.Service.Print;
|
||||
|
||||
namespace YY.Admin.Views.Print;
|
||||
@@ -32,6 +33,8 @@ public partial class PrintPreviewWindow : HandyControl.Controls.Window
|
||||
_printDotService = printDotService;
|
||||
_initialPrinterName = selectedPrinterName;
|
||||
|
||||
WebView.CreationProperties = WebView2UserDataFolder.CreateCreationProperties("PrintPreview");
|
||||
|
||||
TbTemplateName.Text = template.TemplateName ?? "(未命名)";
|
||||
TbTemplateCode.Text = $"编码:{template.TemplateCode} " +
|
||||
$"尺寸:{template.PaperWidthMm ?? 210}×{template.PaperHeightMm ?? 297} mm " +
|
||||
|
||||
@@ -10,6 +10,7 @@ using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using YY.Admin.Core.Services;
|
||||
using YY.Admin.Helper;
|
||||
using YY.Admin.ViewModels.RawMaterialEntry;
|
||||
|
||||
namespace YY.Admin.Views.RawMaterialEntry;
|
||||
@@ -101,6 +102,7 @@ public partial class RawMaterialCardGenerateConfirmWindow : HandyControl.Control
|
||||
Func<RawMaterialCardGeneratePlanRow, string> previewHtmlBuilder)
|
||||
{
|
||||
InitializeComponent();
|
||||
PreviewWebView.CreationProperties = WebView2UserDataFolder.CreateCreationProperties("RawMaterialCardConfirm");
|
||||
_printDotService = printDotService;
|
||||
_previewHtmlBuilder = previewHtmlBuilder;
|
||||
HeaderText = $"共 {planItems.Count} 张,左侧展示即将生成的原材料卡片,右侧展示业务关联打印模板预览。";
|
||||
|
||||
@@ -2,6 +2,7 @@ using System.ComponentModel;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using YY.Admin.Helper;
|
||||
using YY.Admin.ViewModels.RawMaterialEntry;
|
||||
|
||||
namespace YY.Admin.Views.RawMaterialEntry;
|
||||
@@ -14,6 +15,7 @@ public partial class RawMaterialEntryOperationView : UserControl
|
||||
public RawMaterialEntryOperationView()
|
||||
{
|
||||
InitializeComponent();
|
||||
PrintPreviewWebView.CreationProperties = WebView2UserDataFolder.CreateCreationProperties("RawMaterialEntryPreview");
|
||||
Loaded += OnLoaded;
|
||||
DataContextChanged += OnDataContextChanged;
|
||||
Unloaded += (_, _) => DetachVm();
|
||||
|
||||
@@ -59,6 +59,8 @@
|
||||
<ItemGroup>
|
||||
<Reference Include="HandyControl">
|
||||
<HintPath>..\YY.Admin.Core\libs\HandyControl.dll</HintPath>
|
||||
<!-- 单文件宿主下外部 UI 库需外置,否则易出现 pack:// 资源加载失败(启动即退出) -->
|
||||
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
|
||||
@@ -106,10 +108,12 @@
|
||||
</None>
|
||||
<None Update="Updates\version.xml">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<!-- 单文件发布时须外置,否则安装目录无物理文件 -->
|
||||
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<!-- 主程序 BaseDirectory 需含 Configuration/appsettings.json;带 RuntimeIdentifier 时子项目 None 未必进入主输出 -->
|
||||
<!-- 主程序须携带 Configuration/appsettings.json。.NET 单文件发布时 Linked None 的 ExcludeFromSingleFile 可能仍不落盘,故 Publish 后再强制复制一次 -->
|
||||
<ItemGroup>
|
||||
<None Include="..\YY.Admin.Services\Configuration\appsettings.json">
|
||||
<Link>Configuration\appsettings.json</Link>
|
||||
@@ -117,4 +121,13 @@
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="EnsureAppSettingsJsonInPublishDirectory" AfterTargets="Publish">
|
||||
<PropertyGroup>
|
||||
<_AppSettingsSource>$(MSBuildProjectDirectory)\..\YY.Admin.Services\Configuration\appsettings.json</_AppSettingsSource>
|
||||
</PropertyGroup>
|
||||
<Error Condition="!Exists('$(_AppSettingsSource)')" Text="缺少源文件 $(_AppSettingsSource),无法发布 Configuration\appsettings.json。" />
|
||||
<MakeDir Directories="$(PublishDir)Configuration" />
|
||||
<Copy SourceFiles="$(_AppSettingsSource)" DestinationFiles="$(PublishDir)Configuration\appsettings.json" SkipUnchangedFiles="false" />
|
||||
</Target>
|
||||
|
||||
</Project>
|
||||
|
||||
Binary file not shown.
78
yy-admin-master/installer/YY.Admin.Setup.iss
Normal file
78
yy-admin-master/installer/YY.Admin.Setup.iss
Normal file
@@ -0,0 +1,78 @@
|
||||
; Inno Setup 6 安装脚本 — 用法见同目录 build-installer.ps1
|
||||
; 需先安装: https://jrsoftware.org/isdl.php
|
||||
|
||||
#define MyAppName "智能制造MES工控"
|
||||
#define MyAppExeName "YY.Admin.exe"
|
||||
#define MyAppVersion "1.1.0"
|
||||
#define MyPublisher "星数连科技科技有限公司"
|
||||
; 相对本 .iss 文件位置(installer\ 下的上一级为 yy-admin-master)
|
||||
#define PublishRoot "..\YY.Admin\bin\Release\net8.0-windows10.0.19041\win-x64\publish"
|
||||
|
||||
[Setup]
|
||||
AppId={{B7E8F4A2-9C1D-4E6F-8A3B-2D5E9C1F4A7B}
|
||||
AppName={#MyAppName}
|
||||
AppVersion={#MyAppVersion}
|
||||
AppPublisher={#MyPublisher}
|
||||
AppPublisherURL=https://www.example.com/
|
||||
DefaultDirName={autopf}\{#MyPublisher}\{#MyAppName}
|
||||
DefaultGroupName={#MyAppName}
|
||||
AllowNoIcons=yes
|
||||
OutputDir=..\_installer_output
|
||||
OutputBaseFilename=YY.Admin_Setup_{#MyAppVersion}_win-x64
|
||||
Compression=lzma2/max
|
||||
SolidCompression=yes
|
||||
WizardStyle=modern
|
||||
PrivilegesRequired=admin
|
||||
ArchitecturesAllowed=x64compatible
|
||||
ArchitecturesInstallIn64BitMode=x64compatible
|
||||
MinVersion=10.0.17763
|
||||
DisableProgramGroupPage=no
|
||||
DisableDirPage=no
|
||||
UninstallDisplayIcon={app}\{#MyAppExeName}
|
||||
SetupLogging=yes
|
||||
|
||||
; 向导语言:使用 Inno 自带的 Default.isl(英文)。若本机存在 Languages\ChineseSimplified.isl,
|
||||
; 可改为: Name: "chinesesimp"; MessagesFile: "compiler:Languages\ChineseSimplified.isl"
|
||||
[Languages]
|
||||
Name: "english"; MessagesFile: "compiler:Default.isl"
|
||||
|
||||
[Tasks]
|
||||
Name: "desktopicon"; Description: "在桌面创建快捷方式"; GroupDescription: "附加图标:"; Flags: unchecked
|
||||
Name: "installwebview2"; Description: "安装 Microsoft Edge WebView2 运行时(内嵌网页需要;若已安装会自动跳过)"; GroupDescription: "运行环境:"; Flags: checkedonce
|
||||
|
||||
[Files]
|
||||
; 发布目录完整拷贝(含 Configuration、Updates 等)
|
||||
Source: "{#PublishRoot}\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
|
||||
; WebView2 引导安装包(由 build-installer.ps1 下载到 redist;编译时若无文件可加 Flags skipifsourcedoesntexist)
|
||||
Source: "redist\MicrosoftEdgeWebview2Setup.exe"; DestDir: "{tmp}"; Flags: deleteafterinstall skipifsourcedoesntexist
|
||||
|
||||
[Icons]
|
||||
Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; WorkingDir: "{app}"
|
||||
Name: "{group}\卸载 {#MyAppName}"; Filename: "{uninstallexe}"
|
||||
Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; WorkingDir: "{app}"; Tasks: desktopicon
|
||||
|
||||
[Run]
|
||||
Filename: "{tmp}\MicrosoftEdgeWebview2Setup.exe"; Parameters: "/silent /install"; StatusMsg: "正在安装 WebView2 运行时..."; Flags: waituntilterminated; Tasks: installwebview2; Check: ShouldInstallWebView2()
|
||||
|
||||
Filename: "{app}\{#MyAppExeName}"; Description: "启动 {#MyAppName}"; Flags: nowait postinstall skipifsilent
|
||||
|
||||
[Code]
|
||||
|
||||
// 安装包是否携带了 WebView2 引导程序(已释放到临时目录)
|
||||
function WebView2BootstrapInTmp: Boolean;
|
||||
begin
|
||||
Result := FileExists(ExpandConstant('{tmp}\MicrosoftEdgeWebview2Setup.exe'));
|
||||
end;
|
||||
|
||||
// Evergreen WebView2 是否存在(注册表中存在版本号则认为已装)
|
||||
function WebView2RuntimeInstalled: Boolean;
|
||||
var
|
||||
Ver: String;
|
||||
begin
|
||||
Result := RegQueryStringValue(HKLM, 'SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}', 'pv', Ver) and (Trim(Ver) <> '');
|
||||
end;
|
||||
|
||||
function ShouldInstallWebView2: Boolean;
|
||||
begin
|
||||
Result := WebView2BootstrapInTmp and (not WebView2RuntimeInstalled);
|
||||
end;
|
||||
89
yy-admin-master/installer/build-installer.ps1
Normal file
89
yy-admin-master/installer/build-installer.ps1
Normal file
@@ -0,0 +1,89 @@
|
||||
# Release publish + WebView2 bootstrap download + Inno Setup ISCC
|
||||
# Requires Inno Setup 6 (ISCC.exe)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$Root = Split-Path -Parent $PSScriptRoot
|
||||
$Csproj = Join-Path $Root 'YY.Admin\YY.Admin.csproj'
|
||||
$PublishRel = 'YY.Admin\bin\Release\net8.0-windows10.0.19041\win-x64\publish'
|
||||
$PublishDir = Join-Path $Root $PublishRel
|
||||
$RedistDir = Join-Path $PSScriptRoot 'redist'
|
||||
$WebView2Exe = Join-Path $RedistDir 'MicrosoftEdgeWebview2Setup.exe'
|
||||
$WebView2Url = 'https://go.microsoft.com/fwlink/p/?LinkId=2124703'
|
||||
|
||||
Write-Host '>>> dotnet publish (Release, win-x64)...'
|
||||
dotnet publish $Csproj -c Release `
|
||||
-p:IncludeNativeLibrariesForSelfExtract=true `
|
||||
-p:EnableCompressionInSingleFile=true `
|
||||
--verbosity minimal
|
||||
|
||||
if (-not (Test-Path $PublishDir)) {
|
||||
throw "Publish output not found: $PublishDir"
|
||||
}
|
||||
|
||||
$cfgPublish = Join-Path $PublishDir 'Configuration\appsettings.json'
|
||||
if (-not (Test-Path $cfgPublish)) {
|
||||
throw "Missing Configuration\appsettings.json under publish (ExcludeFromSingleFile required). Path: $cfgPublish"
|
||||
}
|
||||
|
||||
New-Item -ItemType Directory -Force -Path $RedistDir | Out-Null
|
||||
if (-not (Test-Path $WebView2Exe)) {
|
||||
Write-Host '>>> Downloading WebView2 Evergreen bootstrapper...'
|
||||
Invoke-WebRequest -Uri $WebView2Url -OutFile $WebView2Exe -UseBasicParsing
|
||||
}
|
||||
|
||||
function Find-InnoCompiler {
|
||||
if ($env:ISCC -and (Test-Path -LiteralPath $env:ISCC)) {
|
||||
return $env:ISCC
|
||||
}
|
||||
if ($env:INNO_SETUP_DIR) {
|
||||
$p = Join-Path $env:INNO_SETUP_DIR.TrimEnd('\') 'ISCC.exe'
|
||||
if (Test-Path -LiteralPath $p) {
|
||||
return $p
|
||||
}
|
||||
}
|
||||
$cmd = Get-Command 'iscc.exe' -ErrorAction SilentlyContinue
|
||||
if ($cmd -and $cmd.Source -and (Test-Path -LiteralPath $cmd.Source)) {
|
||||
return $cmd.Source
|
||||
}
|
||||
$candidates = @(
|
||||
'D:\Inno Setup 6\ISCC.exe'
|
||||
"${env:ProgramFiles(x86)}\Inno Setup 6\ISCC.exe"
|
||||
"${env:ProgramFiles}\Inno Setup 6\ISCC.exe"
|
||||
"${env:LocalAppData}\Programs\Inno Setup 6\ISCC.exe"
|
||||
)
|
||||
foreach ($c in $candidates) {
|
||||
if ($c -and (Test-Path -LiteralPath $c)) {
|
||||
return $c
|
||||
}
|
||||
}
|
||||
return $null
|
||||
}
|
||||
|
||||
$iscc = Find-InnoCompiler
|
||||
|
||||
if (-not $iscc) {
|
||||
throw @'
|
||||
ISCC.exe not found.
|
||||
|
||||
1) Install Inno Setup 6: https://jrsoftware.org/isdl.php
|
||||
2) Or set env ISCC to full path of ISCC.exe, e.g.:
|
||||
set ISCC=C:\Program Files (x86)\Inno Setup 6\ISCC.exe
|
||||
3) Or set INNO_SETUP_DIR to the Inno Setup 6 folder.
|
||||
|
||||
Then run this script again.
|
||||
'@
|
||||
}
|
||||
|
||||
Write-Host ">>> Using ISCC: $iscc"
|
||||
|
||||
$iss = Join-Path $PSScriptRoot 'YY.Admin.Setup.iss'
|
||||
Write-Host '>>> Compiling installer (working dir: installer)...'
|
||||
Push-Location $PSScriptRoot
|
||||
try {
|
||||
& $iscc "`"$iss`""
|
||||
} finally {
|
||||
Pop-Location
|
||||
}
|
||||
|
||||
$outDir = Join-Path $Root '_installer_output'
|
||||
Write-Host ">>> Done. Output folder: $outDir"
|
||||
2
yy-admin-master/installer/redist/.gitignore
vendored
Normal file
2
yy-admin-master/installer/redist/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
# WebView2 Evergreen 引导程序由 build-installer.ps1 自动下载,不必提交仓库
|
||||
MicrosoftEdgeWebview2Setup.exe
|
||||
Reference in New Issue
Block a user