增加了构型选择功能,修改了数据库结构

This commit is contained in:
jinchao 2025-04-30 13:47:35 +08:00
parent f753a8c94d
commit aa42e28173
14 changed files with 336 additions and 48 deletions

Binary file not shown.

View File

@ -1,2 +0,0 @@
USER_ID,USER_NAME,USER_PASSWORD,USER_ACCESS
0,admin,4a0f4f90ee7c4bcfe1aaed38f83270c2e04d24250bf4370617f4cb792a9e5a3b,4
1 USER_ID USER_NAME USER_PASSWORD USER_ACCESS
2 0 admin 4a0f4f90ee7c4bcfe1aaed38f83270c2e04d24250bf4370617f4cb792a9e5a3b 4

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

View File

@ -0,0 +1,171 @@
class HeaderTools extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.render();
this.addEventListeners();
}
// 添加getter方法
get selectedProduct() {
return this.shadowRoot.getElementById('productSelect').value;
}
render() {
this.shadowRoot.innerHTML = `
<style>
:host {
display: flex;
align-items: center;
gap: 12px;
margin-left: auto;
}
.tool-item {
display: flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
border-radius: 4px;
cursor: pointer;
transition: background-color 0.2s;
}
.tool-item:hover {
background-color: rgba(0, 0, 0, 0.05);
}
.tool-item img {
width: 20px;
height: 20px;
}
.product-container {
display: flex;
align-items: center;
gap: 8px;
}
.product-label {
font-size: 14px;
color: #666;
}
.product-select {
min-width: 120px;
height: 32px;
padding: 0 8px;
border: 1px solid #e0e0e0;
border-radius: 4px;
background-color: white;
cursor: pointer;
}
.search-box {
display: flex;
align-items: center;
height: 32px;
border: 1px solid #e0e0e0;
border-radius: 4px;
background-color: white;
padding: 0 8px;
margin-left: 50px; /* 增加与下拉框的间距 */
}
.search-box input {
border: none;
outline: none;
padding: 0 8px;
width: 120px;
}
.search-box img {
width: 16px;
height: 16px;
opacity: 0.5;
}
</style>
<div class="product-container">
<span class="product-label">构型</span>
<select class="product-select" id="productSelect">
<option value="">选择构型</option>
</select>
</div>
<div class="search-box">
<input type="text" placeholder="搜索...">
<img src="assets/icons/png/search_b.png" alt="搜索">
</div>
<div class="tool-item" id="fontSizeBtn" title="调整字体大小">
<img src="assets/icons/png/font_b.png" alt="字体">
</div>
<div class="tool-item" id="themeBtn" title="调整主题">
<img src="assets/icons/png/palette_b.png" alt="主题">
</div>
<div class="tool-item" id="notificationBtn" title="通知">
<img src="assets/icons/png/bell_b.png" alt="通知">
</div>
<div class="tool-item" id="onlineUsersBtn" title="在线用户">
<img src="assets/icons/png/users_b.png" alt="在线用户">
</div>
`;
}
addEventListeners() {
// 加载构型列表
this.loadProducts();
// 字体大小调整
this.shadowRoot.getElementById('fontSizeBtn').addEventListener('click', () => {
this.dispatchEvent(new CustomEvent('font-size-click'));
});
// 主题调整
this.shadowRoot.getElementById('themeBtn').addEventListener('click', () => {
this.dispatchEvent(new CustomEvent('theme-click'));
});
// 通知
this.shadowRoot.getElementById('notificationBtn').addEventListener('click', () => {
this.dispatchEvent(new CustomEvent('notification-click'));
});
// 在线用户
this.shadowRoot.getElementById('onlineUsersBtn').addEventListener('click', () => {
this.dispatchEvent(new CustomEvent('online-users-click'));
});
// 构型选择
this.shadowRoot.getElementById('productSelect').addEventListener('change', (e) => {
this.dispatchEvent(new CustomEvent('product-change', {
detail: { product: e.target.value }
}));
});
}
async loadProducts() {
try {
const response = await fetch('/api/products');
if (!response.ok) {
throw new Error('获取构型列表失败');
}
const products = await response.json();
const select = this.shadowRoot.getElementById('productSelect');
// 清空现有选项
select.innerHTML = '<option value="">选择构型</option>';
products.forEach(product => {
const option = document.createElement('option');
option.value = product.ProductName;
option.textContent = product.ProductName;
select.appendChild(option);
});
} catch (error) {
console.error('加载构型列表失败:', error);
// 可以在这里添加错误提示UI
}
}
}
customElements.define('header-tools', HeaderTools);

View File

@ -393,10 +393,14 @@ class ModelDevelopment extends HTMLElement {
async fetchModels(chapterId) {
try {
const response = await fetch(`/api/chapter-models/${chapterId}`);
const headerTools = document.querySelector('header-tools');
const productName = headerTools.selectedProduct;
const response = await fetch(`/api/chapter-models/${chapterId}?productName=${encodeURIComponent(productName)}`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
this.currentModels = await response.json();
this.currentChapter = this.chapters.find(chapter => chapter.ID === chapterId);
this.renderModels();
@ -408,7 +412,10 @@ class ModelDevelopment extends HTMLElement {
async fetchModelVersions(className, modelName) {
try {
const response = await fetch(`/api/model-versions/${encodeURIComponent(className)}`);
const headerTools = document.querySelector('header-tools');
const productName = headerTools.selectedProduct;
const response = await fetch(`/api/model-versions/${encodeURIComponent(className)}?productName=${encodeURIComponent(productName)}`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
@ -423,6 +430,10 @@ class ModelDevelopment extends HTMLElement {
async saveModelVersion(versionData) {
try {
const headerTools = document.querySelector('header-tools');
const productName = headerTools.selectedProduct;
versionData.ProductName = productName;
const response = await fetch('/api/model-versions', {
method: 'POST',
headers: {
@ -432,12 +443,10 @@ class ModelDevelopment extends HTMLElement {
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.error || `HTTP error! status: ${response.status}`);
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
return result;
return await response.json();
} catch (error) {
console.error('保存模型版本失败:', error);
throw error;
@ -1753,7 +1762,6 @@ class ModelDevelopment extends HTMLElement {
// 组件被重新激活时调用
reactivate() {
console.log('组件被重新激活');
if (this.currentView === 'chapters') {
this.init();
} else if (this.currentView === 'models' && this.currentChapter) {

View File

@ -720,7 +720,7 @@ class ServiceDevelopment extends HTMLElement {
// 组件被重新激活时调用
reactivate() {
console.log('服务开发组件被重新激活');
}
showVersionEditor(versionData) {

View File

@ -142,6 +142,13 @@
height: 20px;
}
/* 添加新的样式 */
.header-right {
display: flex;
align-items: center;
gap: 16px;
}
/* 重置body样式 */
body {
margin: 0;
@ -185,8 +192,11 @@
<img src="assets/icons/png/con_b.png" alt="主页 / 概览" class="icon-small">
<span id="currentPath">主页 / 概览</span>
</div>
<div class="header-right">
<header-tools></header-tools>
<user-info></user-info>
</div>
</div>
<div class="content-area-container">
<tabs-container></tabs-container>
<content-area></content-area>
@ -195,6 +205,7 @@
</div>
</div>
<script src="components/header-tools.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
const tabsContainer = document.querySelector('tabs-container');

View File

@ -22,12 +22,13 @@ router.get('/ata-chapters', (req, res) => {
router.get('/chapter-models/:chapterId', (req, res) => {
try {
const { chapterId } = req.params;
const { productName } = req.query;
if (!chapterId) {
return res.status(400).json({ error: '缺少章节ID参数' });
}
const models = getModelsByChapterId(chapterId);
const models = getModelsByChapterId(chapterId, productName);
res.json(models);
} catch (error) {
console.error('处理章节模型请求时出错:', error);
@ -39,11 +40,13 @@ router.get('/chapter-models/:chapterId', (req, res) => {
router.get('/model-versions/:className', (req, res) => {
try {
const className = req.params.className;
const { productName } = req.query;
if (!className) {
return res.status(400).json({ error: '模型类名不能为空' });
}
const versions = getModelVersionsByClassName(className);
const versions = getModelVersionsByClassName(className, productName);
res.json(versions);
} catch (error) {
console.error(`获取模型版本失败: ${error.message}`);
@ -55,11 +58,19 @@ router.get('/model-versions/:className', (req, res) => {
router.post('/model-versions', (req, res) => {
try {
const versionData = req.body;
const { productName } = req.query;
if (!versionData || !versionData.ClassName) {
return res.status(400).json({ error: '缺少必要的模型版本数据' });
}
// 如果请求中没有productName则使用versionData中的ProductName
if (!productName && versionData.ProductName) {
versionData.ProductName = versionData.ProductName;
} else if (productName) {
versionData.ProductName = productName;
}
const result = saveModelVersion(versionData);
res.json(result);
} catch (error) {

View File

@ -0,0 +1,16 @@
const express = require('express');
const router = express.Router();
const { getProducts } = require('../utils/db-utils');
// 获取所有构型列表
router.get('/products', (req, res) => {
try {
const products = getProducts();
res.json(products);
} catch (error) {
console.error('获取构型列表失败:', error);
res.status(500).json({ error: '获取构型列表失败', details: error.message });
}
});
module.exports = router;

View File

@ -18,6 +18,7 @@ const projectModelRoutes = require('./routes/project-model');
const ataChaptersRoutes = require('./routes/model-dev');
const simulationRoutes = require('./routes/run-simulation');
const udpMonitorRoutes = require('./routes/udp-monitor');
const productsRoutes = require('./routes/products');
const app = express();
const PORT = process.env.PORT || 3000;
@ -66,6 +67,7 @@ app.use('/api', projectModelRoutes);
app.use('/api', ataChaptersRoutes);
app.use('/api', simulationRoutes);
app.use('/api/udp-monitor', udpMonitorRoutes);
app.use('/api', productsRoutes);
// 主页路由
app.get('/', (req, res) => {

View File

@ -30,7 +30,7 @@ function getATAChapters() {
}
// 根据章节ID查询XNModels表中的模型
function getModelsByChapterId(chapterId) {
function getModelsByChapterId(chapterId, productName) {
try {
const xnCorePath = getXNCorePath();
if (!xnCorePath) {
@ -45,13 +45,29 @@ function getModelsByChapterId(chapterId) {
// 打开数据库连接
const db = new Database(dbPath, { readonly: true });
// 查询该章节下的所有模型
const models = db.prepare(`
SELECT Chapters_ID, ModelName, ModelName_CN, Description, ClassName
// 根据productName是否为空构建不同的查询
let query;
let params;
if (!productName || productName === '' || productName === 'undefined') {
query = `
SELECT ProductName, Chapters_ID, ModelName, ModelName_CN, Description, ClassName
FROM 'XNModels'
WHERE Chapters_ID = ?
ORDER BY ModelName
`).all(chapterId);
`;
params = [chapterId];
} else {
query = `
SELECT ProductName, Chapters_ID, ModelName, ModelName_CN, Description, ClassName
FROM 'XNModels'
WHERE Chapters_ID = ? AND ProductName = ?
ORDER BY ModelName
`;
params = [chapterId, productName];
}
const models = db.prepare(query).all(...params);
db.close();
@ -63,7 +79,7 @@ function getModelsByChapterId(chapterId) {
}
// 根据ClassName查询XNModelsVersion表中的模型版本
function getModelVersionsByClassName(className) {
function getModelVersionsByClassName(className, productName) {
try {
const xnCorePath = getXNCorePath();
if (!xnCorePath) {
@ -78,15 +94,33 @@ function getModelVersionsByClassName(className) {
// 打开数据库连接
const db = new Database(dbPath, { readonly: true });
// 查询该类名下的所有版本
const versions = db.prepare(`
SELECT ClassName, Name, Version, CodePath, Author, Description,
// 根据productName是否为空构建不同的查询
let query;
let params;
if (!productName || productName === '') {
query = `
SELECT ProductName, ClassName, Name, Version, CodePath, Author, Description,
CreatTime, ChangeTime, RunFreqGroup, RunNode, Priority,
DataPackagePath, DataPackageHeaderPath, DataPackageEntryPoint, DataPackageInterfaceName
FROM 'XNModelsVersion'
WHERE ClassName = ?
ORDER BY Version DESC
`).all(className);
`;
params = [className];
} else {
query = `
SELECT ProductName, ClassName, Name, Version, CodePath, Author, Description,
CreatTime, ChangeTime, RunFreqGroup, RunNode, Priority,
DataPackagePath, DataPackageHeaderPath, DataPackageEntryPoint, DataPackageInterfaceName
FROM 'XNModelsVersion'
WHERE ClassName = ? AND ProductName = ?
ORDER BY Version DESC
`;
params = [className, productName];
}
const versions = db.prepare(query).all(...params);
db.close();
@ -101,7 +135,7 @@ function getModelVersionsByClassName(className) {
function saveModelVersion(versionData) {
try {
// 验证必填字段
const requiredFields = ['ClassName', 'Name', 'Version', 'Author'];
const requiredFields = ['ClassName', 'Name', 'Version', 'Author', 'ProductName'];
for (const field of requiredFields) {
if (!versionData[field]) {
throw new Error(`${field} 是必填字段`);
@ -126,8 +160,8 @@ function saveModelVersion(versionData) {
// 查询是否存在要更新的版本
const existingVersion = db.prepare(`
SELECT COUNT(*) as count FROM 'XNModelsVersion'
WHERE ClassName = ? AND Version = ?
`).get(versionData.ClassName, versionData.originalVersion || versionData.Version);
WHERE ClassName = ? AND Version = ? AND ProductName = ?
`).get(versionData.ClassName, versionData.originalVersion || versionData.Version, versionData.ProductName);
if (existingVersion.count === 0) {
// 不存在要更新的版本,创建新版本
@ -164,15 +198,16 @@ function saveModelVersion(versionData) {
DataPackagePath = ?,
DataPackageHeaderPath = ?,
DataPackageEntryPoint = ?,
DataPackageInterfaceName = ?
WHERE ClassName = ? AND Version = ?
DataPackageInterfaceName = ?,
ProductName = ?
WHERE ClassName = ? AND Version = ? AND ProductName = ?
`).run(
versionData.Name,
versionData.Version,
versionData.Author,
versionData.Description || '',
versionData.CodePath || '',
changeTime, // 使用前端传来的时间或生成的当前时间
changeTime,
versionData.RunFreqGroup || '',
versionData.RunNode || '',
versionData.Priority || '0',
@ -180,8 +215,10 @@ function saveModelVersion(versionData) {
versionData.DataPackageHeaderPath || '',
versionData.DataPackageEntryPoint || '',
versionData.DataPackageInterfaceName || '',
versionData.ProductName,
versionData.ClassName,
versionData.originalVersion || versionData.Version
versionData.originalVersion || versionData.Version,
versionData.ProductName
);
db.close();
@ -208,8 +245,8 @@ function saveNewVersion(db, versionData) {
// 检查版本是否已存在
const existingVersion = db.prepare(`
SELECT COUNT(*) as count FROM 'XNModelsVersion'
WHERE ClassName = ? AND Version = ?
`).get(versionData.ClassName, versionData.Version);
WHERE ClassName = ? AND Version = ? AND ProductName = ?
`).get(versionData.ClassName, versionData.Version, versionData.ProductName);
if (existingVersion.count > 0) {
db.close();
@ -233,19 +270,20 @@ function saveNewVersion(db, versionData) {
// 插入新版本
const insertResult = db.prepare(`
INSERT INTO 'XNModelsVersion' (
ClassName, Name, Version, CodePath, Author, Description,
ProductName, ClassName, Name, Version, CodePath, Author, Description,
CreatTime, ChangeTime, RunFreqGroup, RunNode, Priority,
DataPackagePath, DataPackageHeaderPath, DataPackageEntryPoint, DataPackageInterfaceName
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`).run(
versionData.ProductName,
versionData.ClassName,
versionData.Name,
versionData.Version,
versionData.CodePath || '',
versionData.Author,
versionData.Description || '',
createTime, // 使用前端传来的创建时间或生成的当前时间
changeTime, // 使用前端传来的修改时间或生成的当前时间
createTime,
changeTime,
versionData.RunFreqGroup || '',
versionData.RunNode || '',
versionData.Priority || '0',
@ -563,6 +601,38 @@ function createService(serviceData) {
}
}
// 查询Products表中的所有产品
function getProducts() {
try {
const xnCorePath = getXNCorePath();
if (!xnCorePath) {
throw new Error('XNCore环境变量未设置无法获取数据库路径');
}
const dbPath = xnCorePath + '/database/XNSim.db';
if (!dbPath) {
throw new Error('无法找到数据库文件');
}
// 打开数据库连接
const db = new Database(dbPath, { readonly: true });
// 查询所有产品
const products = db.prepare(`
SELECT ProductName, Description
FROM 'Products'
ORDER BY ProductName
`).all();
db.close();
return products;
} catch (error) {
console.error('获取产品列表数据失败:', error.message);
throw error;
}
}
module.exports = {
getATAChapters,
getModelsByChapterId,
@ -571,5 +641,6 @@ module.exports = {
getServices,
getServiceVersionsByClassName,
saveServiceVersion,
createService
createService,
getProducts
};