2025-03-14 16:06:20 +08:00
|
|
|
|
#include "dbStructureModel.h"
|
2025-03-17 17:20:10 +08:00
|
|
|
|
#include "mainwindow.h"
|
2025-03-14 16:45:16 +08:00
|
|
|
|
#include "sqlQueryExecutor.h"
|
2025-03-14 16:06:20 +08:00
|
|
|
|
#include "logger.h"
|
|
|
|
|
|
// #include <QSqlDatabase>
|
|
|
|
|
|
// #include <QSqlQuery>
|
|
|
|
|
|
|
|
|
|
|
|
DBStructureModel::DBStructureModel(QObject* parent)
|
2025-03-17 17:20:10 +08:00
|
|
|
|
: QAbstractItemModel(parent)
|
|
|
|
|
|
, m_rootNode(new DBStructureNode(RootNode, "Root"))
|
|
|
|
|
|
, m_pMainWindow(nullptr)
|
2025-03-14 16:06:20 +08:00
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
DBStructureModel::~DBStructureModel()
|
|
|
|
|
|
{
|
|
|
|
|
|
delete m_rootNode;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-03-17 17:20:10 +08:00
|
|
|
|
void DBStructureModel::setMainWindow(MainWindow* window)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pMainWindow = window;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-03-14 16:06:20 +08:00
|
|
|
|
DBStructureNode* DBStructureModel::getNode(const QModelIndex& index) const
|
|
|
|
|
|
{
|
|
|
|
|
|
if(index.isValid())
|
|
|
|
|
|
return static_cast<DBStructureNode*>(index.internalPointer());
|
|
|
|
|
|
else
|
|
|
|
|
|
return m_rootNode;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
DBStructureNode* DBStructureModel::getConnectionNode(const QString& name) const
|
|
|
|
|
|
{
|
|
|
|
|
|
for(int i = 0; i < m_rootNode->childCount(); ++i)
|
|
|
|
|
|
{
|
|
|
|
|
|
DBStructureNode* node = m_rootNode->child(i);
|
|
|
|
|
|
if(node->type() == ConnectionNode && node->name() == name)
|
|
|
|
|
|
return node;
|
|
|
|
|
|
}
|
|
|
|
|
|
return nullptr;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-04-18 18:44:26 +08:00
|
|
|
|
DBStructureNode* DBStructureModel::getModelNode(DBStructureNode* connNode, int modelID) const
|
|
|
|
|
|
{
|
|
|
|
|
|
if(!connNode)
|
|
|
|
|
|
return nullptr;
|
|
|
|
|
|
|
|
|
|
|
|
for(int i = 0; i < connNode->childCount(); ++i)
|
|
|
|
|
|
{
|
|
|
|
|
|
DBStructureNode* node = connNode->child(i);
|
|
|
|
|
|
if(node->type() == TableNode && node->data(Qt::UserRole + NodeDataRole::ID).toInt() == modelID)
|
|
|
|
|
|
return node;
|
|
|
|
|
|
}
|
|
|
|
|
|
return nullptr;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-03-14 16:06:20 +08:00
|
|
|
|
QModelIndex DBStructureModel::index(int row, int column, const QModelIndex& parent) const
|
|
|
|
|
|
{
|
|
|
|
|
|
if(!hasIndex(row, column, parent))
|
|
|
|
|
|
return QModelIndex();
|
|
|
|
|
|
|
|
|
|
|
|
DBStructureNode* parentNode = getNode(parent);
|
|
|
|
|
|
DBStructureNode* childNode = parentNode->child(row);
|
|
|
|
|
|
if(childNode)
|
|
|
|
|
|
return createIndex(row, column, childNode);
|
|
|
|
|
|
else
|
|
|
|
|
|
return QModelIndex();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
QModelIndex DBStructureModel::parent(const QModelIndex& index) const
|
|
|
|
|
|
{
|
|
|
|
|
|
if(!index.isValid())
|
|
|
|
|
|
return QModelIndex();
|
|
|
|
|
|
|
|
|
|
|
|
DBStructureNode* childNode = static_cast<DBStructureNode*>(index.internalPointer());
|
|
|
|
|
|
DBStructureNode* parentNode = childNode->parentNode();
|
|
|
|
|
|
if(parentNode == m_rootNode)
|
|
|
|
|
|
return QModelIndex();
|
|
|
|
|
|
|
|
|
|
|
|
return createIndex(parentNode->row(), 0 ,parentNode);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int DBStructureModel::rowCount(const QModelIndex& parent) const
|
|
|
|
|
|
{
|
|
|
|
|
|
DBStructureNode* parentNode = getNode(parent);
|
|
|
|
|
|
return parentNode->childCount();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int DBStructureModel::columnCount(const QModelIndex& parent) const
|
|
|
|
|
|
{
|
|
|
|
|
|
return DBStructureNode::ColumnCount;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
QVariant DBStructureModel::data(const QModelIndex& index, int role) const
|
|
|
|
|
|
{
|
|
|
|
|
|
if(!index.isValid())
|
|
|
|
|
|
return QVariant();
|
|
|
|
|
|
|
|
|
|
|
|
DBStructureNode* node = static_cast<DBStructureNode*>(index.internalPointer());
|
|
|
|
|
|
|
|
|
|
|
|
switch (role)
|
|
|
|
|
|
{
|
|
|
|
|
|
case Qt::DisplayRole:
|
|
|
|
|
|
return node->columnData(DBStructureNode::ColumnName);
|
|
|
|
|
|
case Qt::DecorationRole:
|
|
|
|
|
|
return node->icon();
|
|
|
|
|
|
default:
|
2025-03-14 16:45:16 +08:00
|
|
|
|
return node->data(role);
|
2025-03-14 16:06:20 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
QVariant DBStructureModel::headerData(int section, Qt::Orientation orientation, int role) const
|
|
|
|
|
|
{
|
|
|
|
|
|
if(orientation == Qt::Horizontal && role == Qt::DisplayRole)
|
|
|
|
|
|
{
|
|
|
|
|
|
switch(section)
|
|
|
|
|
|
{
|
|
|
|
|
|
case DBStructureNode::ColumnName:
|
|
|
|
|
|
return "Name";
|
|
|
|
|
|
default:
|
|
|
|
|
|
return QVariant();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return QVariant();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void DBStructureModel::addConnection(const QString& name, const QString& dbType)
|
|
|
|
|
|
{
|
|
|
|
|
|
beginInsertRows(QModelIndex(), m_rootNode->childCount(), m_rootNode->childCount()); //链接节点是根节点的(m_rootNode)的子节点,根节点的索引是QModelIndex()
|
|
|
|
|
|
|
|
|
|
|
|
DBStructureNode* connectionNode = new DBStructureNode(ConnectionNode, name, m_rootNode);
|
|
|
|
|
|
m_rootNode->appendChild(connectionNode);
|
|
|
|
|
|
|
|
|
|
|
|
endInsertRows(); //该语句之后会触发rowsInserted(const QModelIndex &parent, int first, int last)信号,通知视图刷新对应行
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-04-16 16:37:32 +08:00
|
|
|
|
void DBStructureModel::removeConnection(const QString& name)
|
|
|
|
|
|
{
|
|
|
|
|
|
DBStructureNode* connNode = getConnectionNode(name);
|
|
|
|
|
|
if(!connNode)
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
|
|
beginRemoveRows(QModelIndex(), connNode->row(), connNode->row());
|
|
|
|
|
|
m_rootNode->removeChild(connNode);
|
|
|
|
|
|
endRemoveRows();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-03-14 16:06:20 +08:00
|
|
|
|
QModelIndex DBStructureModel::getConnNodeIndex(const QString& name)
|
|
|
|
|
|
{
|
|
|
|
|
|
DBStructureNode* connNode = getConnectionNode(name);
|
|
|
|
|
|
if(!connNode)
|
|
|
|
|
|
return QModelIndex();
|
|
|
|
|
|
else
|
|
|
|
|
|
return index(connNode->row(), 0, QModelIndex());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void DBStructureModel::addDataModel(const QString& connection, Model& model)
|
|
|
|
|
|
{
|
|
|
|
|
|
QModelIndex connIndex = getConnNodeIndex(connection);
|
|
|
|
|
|
if(!connIndex.isValid())
|
|
|
|
|
|
{
|
|
|
|
|
|
LOG_ERROR("DBStructureModel", QString::fromWCharArray(L"获取ModelIndex失败,节点名称:%1").arg(connection));
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
DBStructureNode* connNode = getConnectionNode(connection);
|
|
|
|
|
|
if(!connNode)
|
|
|
|
|
|
{
|
|
|
|
|
|
LOG_ERROR("DBStructureModel", QString::fromWCharArray(L"获取Node对象失败,节点名称:%1").arg(connection));
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
beginInsertRows(connIndex, connNode->childCount(), connNode->childCount());
|
|
|
|
|
|
|
|
|
|
|
|
DBStructureNode* modelNode = new DBStructureNode(TableNode, model.name, connNode);
|
2025-03-14 16:45:16 +08:00
|
|
|
|
modelNode->setData(Qt::UserRole + NodeDataRole::ID, model.id);
|
2025-03-14 16:06:20 +08:00
|
|
|
|
for(int groupID : model.groups)
|
|
|
|
|
|
{
|
2025-04-02 15:57:00 +08:00
|
|
|
|
//QString groupName = SqlQueryExecutor::instance().getAttributeGroupName(connection, groupID);
|
|
|
|
|
|
AttributeGroup group = SqlQueryExecutor::instance().getAttributeGroupData(connection, groupID);
|
|
|
|
|
|
if(group.name.isEmpty())
|
|
|
|
|
|
continue;
|
|
|
|
|
|
DBStructureNode* groupNode = new DBStructureNode(GroupNode, group.name, modelNode);
|
2025-03-14 16:45:16 +08:00
|
|
|
|
groupNode->setData(Qt::UserRole + NodeDataRole::ID, groupID);
|
2025-04-02 15:57:00 +08:00
|
|
|
|
groupNode->setData(Qt::UserRole + NodeDataRole::Type, group.isPublic);
|
2025-03-14 16:06:20 +08:00
|
|
|
|
modelNode->appendChild(groupNode);
|
|
|
|
|
|
}
|
|
|
|
|
|
connNode->appendChild(modelNode);
|
|
|
|
|
|
|
|
|
|
|
|
endInsertRows();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-03-17 17:20:10 +08:00
|
|
|
|
void DBStructureModel::removeDataModel(DBStructureNode* modelNode)
|
|
|
|
|
|
{
|
|
|
|
|
|
//可以不对node在进行一系列的检查,因为该函数调取之前已有相关逻辑
|
|
|
|
|
|
int modelID = modelNode->data(Qt::UserRole + NodeDataRole::ID).toInt();
|
|
|
|
|
|
//先从数据库中删除
|
|
|
|
|
|
DBStructureNode* connNode = modelNode->parentNode();
|
|
|
|
|
|
if(!(connNode && connNode->type() == ConnectionNode))
|
|
|
|
|
|
{
|
|
|
|
|
|
if(m_pMainWindow)
|
|
|
|
|
|
{
|
|
|
|
|
|
QString error = QString::fromWCharArray(L"删除失败,未找到所在链接节点信息");
|
|
|
|
|
|
m_pMainWindow->showMessageDialog(type_information, QString::fromWCharArray(L"失败"), error);
|
|
|
|
|
|
}
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2025-03-24 18:13:06 +08:00
|
|
|
|
bool result = SqlQueryExecutor::instance().removeModel(connNode->name(), modelID);
|
2025-03-17 17:20:10 +08:00
|
|
|
|
if(!result)
|
|
|
|
|
|
{
|
|
|
|
|
|
if(m_pMainWindow)
|
|
|
|
|
|
{
|
|
|
|
|
|
QString error = QString::fromWCharArray(L"删除失败,详情可见日志文件");
|
|
|
|
|
|
m_pMainWindow->showMessageDialog(type_information, QString::fromWCharArray(L"失败"), error);
|
|
|
|
|
|
}
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
else //从列表中删除
|
|
|
|
|
|
{
|
|
|
|
|
|
beginRemoveRows(index(connNode->row(), 0, QModelIndex()), modelNode->row(), modelNode->row());
|
|
|
|
|
|
connNode->removeChild(modelNode);
|
|
|
|
|
|
endRemoveRows();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-04-18 18:44:26 +08:00
|
|
|
|
void DBStructureModel::addDataGroup(const QString& connection, int modelID, QVector<int> groups)
|
|
|
|
|
|
{
|
|
|
|
|
|
DBStructureNode* connNode = getConnectionNode(connection);
|
|
|
|
|
|
if(!connNode)
|
|
|
|
|
|
{
|
|
|
|
|
|
LOG_ERROR("DBStructureModel", QString::fromWCharArray(L"添加数组时获取ConnectionNode失败,节点名称:%1").arg(connection));
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
DBStructureNode* modelNode = getModelNode(connNode, modelID);
|
|
|
|
|
|
if(!connNode)
|
|
|
|
|
|
{
|
|
|
|
|
|
LOG_ERROR("DBStructureModel", QString::fromWCharArray(L"添加数组时获取ModelNode失败,节点ID:%1").arg(modelID));
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
QModelIndex connIndex = index(connNode->row(), 0, QModelIndex());
|
|
|
|
|
|
QModelIndex modelIndex = index(modelNode->row(), 0, connIndex);
|
2025-05-07 17:34:27 +08:00
|
|
|
|
beginInsertRows(modelIndex, modelNode->childCount(), modelNode->childCount() + groups.count() - 1);
|
2025-04-18 18:44:26 +08:00
|
|
|
|
|
|
|
|
|
|
for(int groupID : groups)
|
|
|
|
|
|
{
|
|
|
|
|
|
//QString groupName = SqlQueryExecutor::instance().getAttributeGroupName(connection, groupID);
|
|
|
|
|
|
AttributeGroup group = SqlQueryExecutor::instance().getAttributeGroupData(connection, groupID);
|
|
|
|
|
|
if(group.name.isEmpty())
|
|
|
|
|
|
continue;
|
|
|
|
|
|
DBStructureNode* groupNode = new DBStructureNode(GroupNode, group.name, modelNode);
|
|
|
|
|
|
groupNode->setData(Qt::UserRole + NodeDataRole::ID, groupID);
|
|
|
|
|
|
groupNode->setData(Qt::UserRole + NodeDataRole::Type, group.isPublic);
|
|
|
|
|
|
modelNode->appendChild(groupNode);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
endInsertRows();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-04-17 15:34:43 +08:00
|
|
|
|
void DBStructureModel::removeDataGroup(DBStructureNode* groupNode)
|
|
|
|
|
|
{
|
|
|
|
|
|
int groupID = groupNode->data(Qt::UserRole + NodeDataRole::ID).toInt();
|
|
|
|
|
|
//先从数据库中删除
|
|
|
|
|
|
DBStructureNode* modelNode = groupNode->parentNode();
|
|
|
|
|
|
if(!(modelNode && modelNode->type() == TableNode))
|
|
|
|
|
|
{
|
|
|
|
|
|
if(m_pMainWindow)
|
|
|
|
|
|
{
|
|
|
|
|
|
QString error = QString::fromWCharArray(L"删除失败,未找到所在链接节点信息");
|
|
|
|
|
|
m_pMainWindow->showMessageDialog(type_information, QString::fromWCharArray(L"失败"), error);
|
|
|
|
|
|
}
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
int modelID = modelNode->data(Qt::UserRole + NodeDataRole::ID).toInt();
|
|
|
|
|
|
DBStructureNode* connNode = modelNode->parentNode();
|
|
|
|
|
|
if(!(connNode && connNode->type() == ConnectionNode))
|
|
|
|
|
|
{
|
|
|
|
|
|
if(m_pMainWindow)
|
|
|
|
|
|
{
|
|
|
|
|
|
QString error = QString::fromWCharArray(L"删除失败,未找到所在链接节点信息");
|
|
|
|
|
|
m_pMainWindow->showMessageDialog(type_information, QString::fromWCharArray(L"失败"), error);
|
|
|
|
|
|
}
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
bool result = SqlQueryExecutor::instance().removeAttributeGroup(connNode->name(), modelID, groupID);
|
|
|
|
|
|
if(!result)
|
|
|
|
|
|
{
|
|
|
|
|
|
if(m_pMainWindow)
|
|
|
|
|
|
{
|
|
|
|
|
|
QString error = QString::fromWCharArray(L"删除失败,详情可见日志文件");
|
|
|
|
|
|
m_pMainWindow->showMessageDialog(type_information, QString::fromWCharArray(L"失败"), error);
|
|
|
|
|
|
}
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
else //从列表中删除
|
|
|
|
|
|
{
|
|
|
|
|
|
QModelIndex connIndex = index(connNode->row(), 0, QModelIndex());
|
|
|
|
|
|
beginRemoveRows(index(modelNode->row(), 0, connIndex), groupNode->row(), groupNode->row());
|
|
|
|
|
|
modelNode->removeChild(groupNode);
|
|
|
|
|
|
endRemoveRows();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-03-14 16:06:20 +08:00
|
|
|
|
void DBStructureModel::refreshStructure_Connection(const QString& connection)
|
|
|
|
|
|
{
|
|
|
|
|
|
DBStructureNode* connNode = getConnectionNode(connection);
|
|
|
|
|
|
if(!connNode)
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
|
|
//先删除当前链接节点下的节点
|
|
|
|
|
|
if(connNode->childCount() > 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
beginRemoveRows(index(connNode->row(), 0, QModelIndex()), 0, connNode->childCount() - 1);
|
|
|
|
|
|
connNode->removeAllChildren();
|
|
|
|
|
|
endRemoveRows(); //该语句之后会触发rowsRemoved(const QModelIndex &parent, int first, int last)信号,通知视图刷新对应行
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
QSqlDatabase db = QSqlDatabase::database(connection);
|
|
|
|
|
|
if(!db.isOpen())
|
|
|
|
|
|
{
|
|
|
|
|
|
if(connNode->status() == Connect)
|
|
|
|
|
|
{
|
|
|
|
|
|
connNode->setStatus(Disconnect);
|
|
|
|
|
|
QModelIndex topLeft = index(connNode->row(), 0, QModelIndex());
|
|
|
|
|
|
QModelIndex bottomRight = index(connNode->row(), 0, QModelIndex());
|
|
|
|
|
|
emit dataChanged(topLeft, bottomRight, {Qt::DecorationRole});
|
|
|
|
|
|
}
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//重新加载数据节点
|
|
|
|
|
|
/*QString strSql = "SELECT * FROM basic.attribute_group ORDER BY id ASC";
|
|
|
|
|
|
QSqlQuery query(db);
|
|
|
|
|
|
if(!query.exec(strSql))
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
|
|
QVector<DBStructureNode*> tables;
|
|
|
|
|
|
while(query.next())
|
|
|
|
|
|
{
|
|
|
|
|
|
QString tableName = query.value(2).toString();
|
|
|
|
|
|
tables << new DBStructureNode(TableNode, tableName, connNode);
|
|
|
|
|
|
}*/
|
|
|
|
|
|
|
|
|
|
|
|
const QVector<Model> models = SqlQueryExecutor::instance().getModels(connection);
|
|
|
|
|
|
beginInsertRows(index(connNode->row(), 0, QModelIndex()), 0, models.count() - 1);
|
|
|
|
|
|
|
|
|
|
|
|
for(const Model& model : models)
|
|
|
|
|
|
{
|
|
|
|
|
|
DBStructureNode* modelNode = new DBStructureNode(TableNode, model.name, connNode);
|
2025-03-17 17:20:10 +08:00
|
|
|
|
modelNode->setData(Qt::UserRole + NodeDataRole::ID, model.id);
|
2025-03-14 16:06:20 +08:00
|
|
|
|
for(int groupID : model.groups)
|
|
|
|
|
|
{
|
2025-04-02 15:57:00 +08:00
|
|
|
|
//QString groupName = SqlQueryExecutor::instance().getAttributeGroupName(connection, groupID);
|
|
|
|
|
|
AttributeGroup group = SqlQueryExecutor::instance().getAttributeGroupData(connection, groupID);
|
|
|
|
|
|
if(group.name.isEmpty())
|
|
|
|
|
|
continue;
|
|
|
|
|
|
DBStructureNode* groupNode = new DBStructureNode(GroupNode, group.name, modelNode);
|
2025-04-01 14:37:41 +08:00
|
|
|
|
groupNode->setData(Qt::UserRole + NodeDataRole::ID, groupID);
|
2025-04-02 15:57:00 +08:00
|
|
|
|
groupNode->setData(Qt::UserRole + NodeDataRole::Type, group.isPublic);
|
2025-03-14 16:06:20 +08:00
|
|
|
|
modelNode->appendChild(groupNode);
|
|
|
|
|
|
}
|
|
|
|
|
|
connNode->appendChild(modelNode);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
endInsertRows();
|
|
|
|
|
|
|
|
|
|
|
|
if(connNode->status() == Disconnect)
|
|
|
|
|
|
{
|
|
|
|
|
|
connNode->setStatus(Connect);
|
|
|
|
|
|
QModelIndex topLeft = index(connNode->row(), 0, QModelIndex());
|
|
|
|
|
|
QModelIndex bottomRight = index(connNode->row(), 0, QModelIndex());
|
|
|
|
|
|
emit dataChanged(topLeft, bottomRight, {Qt::DecorationRole});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
}
|