SECS/GEM实现(一)半导体通讯协议软件,C、C++使用介绍

这是一个关于SECS/GEM软件的详细说明,涵盖了软件的C#和C++支持,以及如何添加模块、设置运行环境。软件提供了高速Transaction通讯、多线程处理和DLL元件等功能,并支持Microsoft Visual C++ 6.0及更高版本。初始化过程包括连接服务器、参数设置和通讯状态管理。此外,还涉及了各种回调函数和窗口管理。
摘要由CSDN通过智能技术生成

SECS/GEM 原生态支持C#、C++

  1. 软件特点:
  2. 符合SEMI E5、E30、E37 的 SECS 通讯规范
  3. 提供高速 Transaction 的通讯能力,高性能多线程并发处理Transaction
  4. 提供 DLL 元件供程式设计者使用
  5. 快速 Encode 与 Decode Big Message
  6. 提供VC++、C#等程式语言,提供程式的.Sample Code让程式设计师参考

软件下载地址 www.secsgem.cn

  1. 开发使用
    2.1 添加模块
    SECS/GEM 将项目中secs文件拷贝至自己的项目中,同时引入项目
    \EquipViewApp\EquipViewApp\secs
    |- Group.h
    |- SecsBase.cpp
    |- SecsBase.h
    |- SecsEquip.cpp
    |- SecsEquip.h
    |- SecsPortExport.h
    |- SecsPort.lib (x86/x64) 该lib放置在demo对应的版本中

2.2 运行环境
SECS/GEM Demo对应版本的所有文件都需要拷贝到运行目录下

  1. JngServer文件夹
  2. SecsSystemConfig文件夹
  3. ClientConnect.dll
  4. ErrInfo.dll
  5. SecsPort.dll
  6. Common.dll

SECS/GEM软件完美的支持Microsoft Visual C++6.0 build C++ 6以及后面的版本软件
在这里插入图片描述
SECS/GEM 测试软件界面

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

功能清单
在这里插入图片描述

协议支持
在这里插入图片描述

// EquipViewAppDlg.cpp : 实现文件
//

#include “stdafx.h”
#include “EquipViewApp.h”
#include “EquipViewAppDlg.h”
#include “afxdialogex.h”
#include “ChildView/ConnectControlView.h”
#include “ChildView/SecsParamentView.h”
#include “ChildView/AlarmView.h”
#include “ChildView/EventView.h”
#include “ChildView/VariableView.h”
#include “ChildView/RemoteView.h”
#include “ChildView/PPTransferView.h”
#include “ChildView/TerminalView.h”
#include “ChildView/LogView.h”
#include “ChildView/ConstantView.h”
#include “ChildView/ThreadTestView.h”
#include “ChildView/PPFormatView.h”
#include “ChildView/AutoSimpleView.h”

#ifdef _DEBUG
#define new DEBUG_NEW
#endif

#define TEST_BY_THREAD

// SECS/GEM CEquipViewAppDlg 对话框

CEquipViewAppDlg::CEquipViewAppDlg(CWnd* pParent / =NULL/)
CMyDialogEx(CEquipViewAppDlg::IDD, pParent)
{
m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
m_activeView = nullptr;

}

void CEquipViewAppDlg::DoDataExchange(CDataExchange* pDX)
{
CMyDialogEx::DoDataExchange(pDX);

DDX_Control(pDX, IDC_TREE_FUNCTION, m_treeCtrl);
DDX_Control(pDX, IDC_TAB_CHILD, m_tabChild);
DDX_Control(pDX, IDC_STATIC_TITLE, m_staticTitle);

DDX_Control(pDX, IDC_STATIC_COMM_STATE, m_staticCommState);
DDX_Control(pDX, IDC_STATIC_CONTROL_STATE, m_staticControlState);
DDX_Control(pDX, IDC_EDIT_COMM_STATE, m_editCommState);
DDX_Control(pDX, IDC_EDIT_CONTROL_STATE, m_editControlState);

}

BEGIN_MESSAGE_MAP(CEquipViewAppDlg, CMyDialogEx)
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
ON_NOTIFY(TVN_SELCHANGED, IDC_TREE_FUNCTION, OnTvnSelchangedTreeFunction)
ON_WM_DESTROY()
ON_MESSAGE(ON_COMM_CHANGE, OnStateChange)
ON_MESSAGE(ON_CONTROL_CHANGE, OnStateChange)
END_MESSAGE_MAP()

// CEquipViewAppDlg 消息处理程序

// 数据类型
// L
// A data1
// A data2
// …
// A dataN
bool listData(string pListData/* 发送该的Code对应数据 */, list& listDataDes)
{
vector vecTmpData;
RcResult rc = listSplit(pListData, vecTmpData);
if(rc.rc != 0)
{
// 处理失败,解析数据失败
return false;
}

// 解析数据
string pTmp;
for (unsigned int i = 1; i < vecTmpData.size(); i++)
{
	pTmp = listElement(vecTmpData[i], 1);
	listDataDes.push_back(pTmp);
}
return true;

}

BOOL CEquipViewAppDlg::OnInitDialog()
{
CMyDialogEx::OnInitDialog();

// 设置此对话框的图标。当应用程序主窗口不是对话框时,框架将自动
//  执行此操作
SetIcon(m_hIcon, TRUE);			// 设置大图标
SetIcon(m_hIcon, FALSE);		// 设置小图标
SetName();	
ShowWindow(SW_SHOWNOACTIVATE);

SetNewCreateType(false);


// 测试
string pListData;
list<string> listDataDes;

pListData = "L {A 123} {A KKIX} {A A2}";

listData(pListData, listDataDes);

// 初始化连接 SECS
InitConnect();

InitChildArea();
AddView();
InitTree();

// 初始化子窗口
InitCtrl();
InitChildView();


return TRUE;  // 除非将焦点设置到控件,否则返回 TRUE

}

// 如果向对话框添加最小化按钮,则需要下面的代码
// 来绘制该图标。对于使用文档/视图模型的 MFC 应用程序,
// 这将由框架自动完成。

void CEquipViewAppDlg::OnPaint()
{
if (IsIconic())
{
CPaintDC dc(this); // 用于绘制的设备上下文

	SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);

	// 使图标在工作区矩形中居中
	int cxIcon = GetSystemMetrics(SM_CXICON);
	int cyIcon = GetSystemMetrics(SM_CYICON);
	CRect rect;
	GetClientRect(&rect);
	int x = (rect.Width() - cxIcon + 1) / 2;
	int y = (rect.Height() - cyIcon + 1) / 2;

	// 绘制图标
	dc.DrawIcon(x, y, m_hIcon);
}
else
{
	CDialogEx::OnPaint();
}

}

//当用户拖动最小化窗口时系统调用此函数取得光标
//显示。
HCURSOR CEquipViewAppDlg::OnQueryDragIcon()
{
return static_cast(m_hIcon);
}

void CEquipViewAppDlg::InitCtrl()
{
m_editControlState.SetWindowText(“OFF-LINE”);

if (g_appdata.m_bCommEnable)
{
	string text = GetCommunicationText(eCOMM_ENABLED_NOT_COMMUNICATING);		
	m_editCommState.SetWindowText(text.c_str());
}

}

// 初始化子窗口
void CEquipViewAppDlg::AddView()
{
AddChildView(“Event”, new CEventView);
AddChildView(“Connect”, new CConnectControlView);
AddChildView(“Remote”, new CRemoteView);
AddChildView(“Terminal”, new CTerminalView);
AddChildView(“Variable”, new CVariableView);
AddChildView(“Alarm”, new CAlarmView);
AddChildView(“PP Transfer”, new CPPTransferView);
AddChildView(“SECS Parament”, new CSecsParamentView);
AddChildView(“Constant”, new CConstantView);
AddChildView(“PP Format”, new CPPFormatView);
AddChildView(“Auto Simple”, new CAutoSimpleView);
// test

#ifdef TEST_BY_THREAD
AddChildView(“temp test”, new CThreadTestView);
#endif

// 设置第一个窗口
SetActiveView("Connect");

}

// 初始化树
void CEquipViewAppDlg::InitTree()
{
HTREEITEM hItem;
// 一级对象
hItem = AddItem(“Equip”, nullptr);
// 二级子对象
AddItem(“Event”, hItem);
AddItem(“Variable”, hItem);
AddItem(“Constant”, hItem);
AddItem(“Alarm”, hItem);
AddItem(“Remote”, hItem);
AddItem(“Terminal”, hItem);
AddItem(“PP Transfer”, hItem);
AddItem(“PP Format”, hItem);

// 一级对象
hItem = AddItem("Connect", nullptr);
AddItem("SECS Parament", hItem);
AddItem("Auto Simple", hItem);

// test
#ifdef TEST_BY_THREAD
AddItem(“temp test”, hItem);
#endif

ExpandAllTree();

}

void CEquipViewAppDlg::ExpandAllTree()
{
HTREEITEM hItem = m_treeCtrl.GetFirstVisibleItem();
while (hItem != nullptr)
{
m_treeCtrl.Expand(hItem, TVE_EXPAND);
hItem = m_treeCtrl.GetNextItem(hItem, TVGN_NEXT);
}
}

// 初始化窗口位置与大小
void CEquipViewAppDlg::InitChildArea()
{
m_tabChild.GetWindowRect(m_rChildRect);
CRect rect;
GetWindowRect(rect);

// 计算长宽
m_rChildRect.bottom		-= m_rChildRect.top;
m_rChildRect.right		-= m_rChildRect.left;

// 位置偏移
m_rChildRect.top		-= 26;
m_rChildRect.left		-= 2;

// 计算位置
m_rChildRect.left		-= rect.left;
m_rChildRect.top		-= rect.top;
m_rChildRect.bottom		+= m_rChildRect.top;
m_rChildRect.right		+= m_rChildRect.left;

}

// 发送消息给窗口
LRESULT CEquipViewAppDlg::SendMsgToView(string pViewName, UINT message,
WPARAM wParam /* = 0 /, LPARAM lParam / = 0 /)
{
CMyDialogEx
pView = GetView(pViewName);
if(pView != nullptr)
{
if(IsWindow(pView->m_hWnd))
{
return pView->SendMessage(message, wParam, lParam);
}
}

return 0;

}

// 发送消息给窗口
bool CEquipViewAppDlg::PostMsgToView(string pViewName, UINT message,
WPARAM wParam /* = 0 /, LPARAM lParam / = 0 /)
{
CMyDialogEx
pView = GetView(pViewName);
if(pView != nullptr)
{
if(IsWindow(pView->m_hWnd))
{
return ((pView->PostMessage(message, wParam, lParam)) == 0);
}
}
return false;
}

// 初始化通讯
bool CEquipViewAppDlg::InitConnect()
{
try
{
m_pSecs = new CSecsEquip();
}
catch (…)
{
m_pSecs = nullptr;
}
if (m_pSecs == nullptr)
{
MessageBox(“错误”, “创建secs通讯对象失败”);

	// 暴力退出不推荐
	exit(0);
}

// 连接服务器
RcResult rc = m_pSecs->Start();

if (rc.rc != 0)
{
	MessageBox(rc.ToString().c_str(), "JNG_Server.exe没有初始化");
	
	// 暴力退出不推荐
	exit(0);
}

// 初始化其他
// 设置参数
rc = m_pSecs->SetIP(g_appdata.m_pAddress); SHOW_MSG_RC(rc);
rc = m_pSecs->SetPort(g_appdata.m_nPort); SHOW_MSG_RC(rc);
rc = m_pSecs->SetPassive(g_appdata.m_bPassive); SHOW_MSG_RC(rc);
rc = m_pSecs->SetDeviceID(g_appdata.m_nDeviceID); SHOW_MSG_RC(rc);
rc = m_pSecs->SetMDLN(g_appdata.m_pInterfaceName); SHOW_MSG_RC(rc);
rc = m_pSecs->SetEnableLog(g_appdata.m_bEnableLog); SHOW_MSG_RC(rc);

rc = m_pSecs->SetT1(g_appdata.m_nTimeout[0]);			   SHOW_MSG_RC(rc);
rc = m_pSecs->SetT2(g_appdata.m_nTimeout[1]);			   SHOW_MSG_RC(rc);
rc = m_pSecs->SetT3(g_appdata.m_nTimeout[2]);			   SHOW_MSG_RC(rc);
rc = m_pSecs->SetT4(g_appdata.m_nTimeout[3]);			   SHOW_MSG_RC(rc);
rc = m_pSecs->SetT5(g_appdata.m_nTimeout[4]);			   SHOW_MSG_RC(rc);
rc = m_pSecs->SetT6(g_appdata.m_nTimeout[5]);			   SHOW_MSG_RC(rc);
rc = m_pSecs->SetT7(g_appdata.m_nTimeout[6]);			   SHOW_MSG_RC(rc);

// 设置winrar.exe的路径
// 如果recipe是单个文档,无需设置
m_pSecs->SetWinrarPath("C:/Program Files/WinRAR/WinRAR.exe");

// 设置回调
m_pSecs->SetClientData(this);
m_pSecs->m_pTerminalCallback = ::OnTerminalProc;
m_pSecs->m_pStateChangeCallback = ::OnStateChangeProc;
m_pSecs->m_pRemoteCallback = ::OnRemoteProc;

// 示例的回调
m_pSecs->m_pConstantsChangeCallback = ::OnConstantChangeProc;
m_pSecs->m_pVarValueProc = ::OnVarValueProc;
m_pSecs->m_pConstantValueProc = ::OnVarValueProc;
m_pSecs->m_pPPNameListProc = ::OnPPNameListProc;
m_pSecs->m_pPPEventProc = ::OnSecsPPEventProc;
// 示例的回调内容结束

// 特殊
m_pSecs->m_pPPFormatValueProc = OnPPFormatValueProc;

// 通过Csv文件加载配置
rc = m_pSecs->LoadDataByCsvFile();
SHOW_MSG_RC(rc);

// test代码,请忽略
TestCode();

// 建立通讯
rc = m_pSecs->SetControlMode((CONTROL_MODE)g_appdata.m_nControlMode);
SHOW_MSG_RC(rc);

if(g_appdata.m_bCommEnable)
{
	rc = m_pSecs->CommEnable();
	SHOW_MSG_RC(rc);
}

return true;

}

// 测试代码
// 请忽略
void CEquipViewAppDlg::TestCode()
{
RcResult rc;

// 测试加载参数,测试代码
TestData();
TestReportLinke();
m_pSecs->LoadSecsConfig();

// m_pSecs->LoadSecsConfig(“D:\SECS Soft\SECS debug\SecsSystemConfig/TSMC/”);

// m_pSecs->SetLogDir("C:\\A\\");

// 设置自定义处理PP事务
m_pSecs->PPHandleMode(::eCostomizeHandle);

}

// 测试数据
void CEquipViewAppDlg::TestData()
{
RcResult rc;

// method handle
rc = m_pSecs->VariableAdd(1260, "method_test", "method test", "A", "1", "um");		   SHOW_MSG_RC(rc);
rc = m_pSecs->VariableAdd(1261, "addr", "method test", "U4", "1", "um");			   SHOW_MSG_RC(rc);
rc = m_pSecs->VariableAdd(1270, "addr", "method test", "L", "", "um");				   SHOW_MSG_RC(rc);
rc = m_pSecs->VariableAdd(2200, "addr", "method test", "A", "1", "");			   SHOW_MSG_RC(rc);


rc = m_pSecs->VariableSetMethod(1260, ::OnVarValueProc);							   SHOW_MSG_RC(rc);
rc = m_pSecs->VariableSetMethod(1261, ::OnVarValueProc);							   SHOW_MSG_RC(rc);
rc = m_pSecs->VariableSetMethod(1270, ::OnVarValueProc);							   SHOW_MSG_RC(rc);
	
rc = m_pSecs->ConstantsAdd(4004, "Constant 4004", "Constants", "A", "", "", "", "");   SHOW_MSG_RC(rc);

rc = m_pSecs->ConstantsSetMethod(4004, ::OnVarValueProc);							   SHOW_MSG_RC(rc);

}

// 报表关联例子
void CEquipViewAppDlg::TestReportLinke()
{
RcResult rc;

// 定义报表
std::vector<int> varID;
varID.push_back(1260);
varID.push_back(1261);
rc = m_pSecs->ReportDefine(5000, varID);		 SHOW_MSG_RC(rc);

varID.clear();	
varID.push_back(1261);
rc = m_pSecs->ReportDefine(5001, varID);		 SHOW_MSG_RC(rc);

varID.clear();	
varID.push_back(672);
rc = m_pSecs->ReportDefine(5002, varID);		 SHOW_MSG_RC(rc);


// 事件-报表关联	
rc = m_pSecs->EventReportLink(8004, 5001);		 SHOW_MSG_RC(rc);
rc = m_pSecs->EventReportLink(1150, 5002);		 SHOW_MSG_RC(rc);

}

void CEquipViewAppDlg::OnTvnSelchangedTreeFunction(NMHDR *pNMHDR, LRESULT *pResult)
{
LPNMTREEVIEW pNMTreeView = reinterpret_cast(pNMHDR);
HTREEITEM hItem = pNMTreeView->itemNew.hItem;
string pSel = m_treeCtrl.GetItemText (hItem);

// 显示选中页面
SetActiveView(pSel);

*pResult = 0;

}

// 增加显示对象
HTREEITEM CEquipViewAppDlg::AddItem(string name, HTREEITEM hItem)
{
HTREEITEM hChild = nullptr;
if(hItem == nullptr)
{
hChild = m_treeCtrl.InsertItem(name.c_str(), NULL, NULL);

}
else
{
	hChild = m_treeCtrl.InsertItem(name.c_str(), NULL, NULL, hItem);			
}	
return hChild;

}

// 增加窗口
void CEquipViewAppDlg::AddChildView(string name, CMyDialogEx* pView)
{
if(pView == nullptr)
{
return ;
}

pView->SetMainView(this);				//  自己就是主界面
pView->SetSecs(m_pSecs);
pView->SetConnectName(m_pConnectName);
pView->Create(pView->GetIDD());
AddMap(name, pView);

}

// 显示指定窗口
void CEquipViewAppDlg::SetActiveView(string name)
{
CMyDialogEx* pView = GetViewMap(name);
if (pView == nullptr)
{
return;
}

// 隐藏之前的窗口
if(m_activeView != nullptr)
{
	m_activeView->ShowWindow(SW_HIDE);
}

// 新窗口
pView->SetParent(this);
pView->ShowWindow(SW_SHOW);
m_activeView = pView;

// 设置标题
string pTitle = pView->GetTitle();
m_staticTitle.SetWindowText(pTitle.c_str());

// 设置位置
pView->MoveWindow(m_rChildRect.left, m_rChildRect.top, 
	m_rChildRect.right - m_rChildRect.left, m_rChildRect.bottom - m_rChildRect.top, TRUE); 

}

// 映射操作
void CEquipViewAppDlg::AddMap(string name, CMyDialogEx* pView)
{
map<string, CMyDialogEx*>::iterator ite;

vLocker lock(&m_syncMapOpt);
ite = m_mapChildView.find(name);
if(ite != m_mapChildView.end())
{
	m_mapChildView.erase(ite);
}
m_mapChildView.insert(make_pair(name, pView));

}

// SECS/GEM获取窗口
CMyDialogEx* CEquipViewAppDlg::GetViewMap(string name)
{
map<string, CMyDialogEx*>::iterator ite;

vLocker lock(&m_syncMapOpt);
ite = m_mapChildView.find(name);
if(ite != m_mapChildView.end())
{
	return ite->second;
}
return nullptr;

}

// SECS/GEM关闭窗口
// SECS/GEM释放资源
BOOL CEquipViewAppDlg::DestroyWindow()
{
// 关闭窗口
CMyDialogEx* pView;
map<string, CMyDialogEx*>::iterator ite;
for (ite = m_mapChildView.begin(); ite != m_mapChildView.end(); ite++)
{
pView = ite->second;
if(pView != nullptr)
{
pView->DestroyWindow();
}
}

// 释放SECS
if(m_pSecs != nullptr)
{
	m_pSecs->Abort();
	delete m_pSecs;
}

g_appdata.Save();

// 调用父类释放自己
return CMyDialogEx::DestroyWindow();

}

// 初始化子窗口
void CEquipViewAppDlg::InitChildView()
{
CMyDialogEx* pView;
map<string, CMyDialogEx*>::iterator ite;
for (ite = m_mapChildView.begin(); ite != m_mapChildView.end(); ite++)
{
pView = ite->second;
if(pView != nullptr)
{
pView->OnInitView();
}
}
}

// 获取窗口
CMyDialogEx* CEquipViewAppDlg::GetView(string name)
{
CMyDialogEx* pView = nullptr;
map<string, CMyDialogEx*>::iterator ite;
ite = m_mapChildView.find(name);
if(ite != m_mapChildView.end())
{
pView = ite->second;
}
return pView;
}

const char* CEquipViewAppDlg::SecsVarValue(int varID)
{
SYSTEMTIME time;
GetLocalTime(&time);

static CString result;
switch (varID) 
{
case 1260: result = ::g_appdata.GetExePath().c_str(); break;
case 1261: result = IntToString(time.wSecond).c_str(); break;	
case 4004: result.Format("dynamic constant value  %d", time.wMilliseconds); break;

case 1270: 
	{
		// L 类型变量示例
		// 极其少情况下使用到

		string nMinute = listJoin("A", IntToString(time.wMinute).c_str());
		string nSec = listJoin("U4", IntToString(time.wSecond).c_str());
		string nMilliseconds = listJoin("I4", IntToString(time.wMilliseconds).c_str());

		string pLTypeTmp = listJoin(nMinute.c_str(), 
			nSec.c_str(), 
			nMilliseconds.c_str());
		result = pLTypeTmp.c_str(); 
	}
	break;

default:
	{
		result = "";		
		CString pTmp;
		pTmp.Format("error variable %d, please contact the engineer", varID);
		OutputDebugString(pTmp);
	}
	break;
}	

return (LPCSTR)result;

}

LRESULT CEquipViewAppDlg::OnStateChange(WPARAM wp, LPARAM lp)
{
if(wp == ::eCommStateChange)
{
string text = GetCommunicationText((COMM_STATE)lp);
m_editCommState.SetWindowText(text.c_str());
}

else if(wp == ::eControlStateChange)
{
	string text = GetControlText((CONTROL_STATE)lp);			
	m_editControlState.SetWindowText(text.c_str());	
}

return 0;

}

// 设置标题
void CEquipViewAppDlg::SetName()
{
CString title;
title = “EquipViewApp( www.semisecs.com )”;
SetWindowText(title);
}

// 参数的类型
// listParament参数的列表
string RecipeJoin(list listParament, string pDataFormat)
{
string pTmp;
list listSML;
list::iterator ite;
listSML.push_back(“L”);

for (ite = listParament.begin(); ite != listParament.end(); ite++)
{
	pTmp = listJoin(pDataFormat.c_str(), ite->c_str());
	listSML.push_back(pTmp);
}

pTmp = listJoin(listSML);
return pTmp;

}

// 服务器请求设备上存在的recipe参数
// 调用顺序如下
// 回调函数保证数据是每次询问完一次recipe才会到下一次
const char* CEquipViewAppDlg::SecsPPFormatValue(PP_FORMAT_CMD pp_cmd, const char* pCmd)
{
if(pp_cmd == eLOAD_TMP_PPID)
{
// 第一步:加载硬盘的recipe到一个recipe临时变量里面
int nRS = SendMsgToView(“PP Format”, ON_PP_FORMAT, 0, (LPARAM)pCmd);
if(nRS == 0)
{
return “0”;
}
}
else if(pp_cmd == eGET_PPID_VALUE)
{
// 第二步:回调会咨询每一个FormatPP.csv里面ID的值
// 将ID映射到临时recipe变量的参数值,作为返回值返回
int nCmdCode = ::atoi(pCmd);
switch(nCmdCode)
{
case 1: return ::g_statu.param1.c_str();
case 2: return ::g_statu.param2.c_str();
case 3:
{
list listParament;
string pDataFormat = “A”;
listParament.push_back(“1.2”);
listParament.push_back(“31.223”);
listParament.push_back(“11.21”);
listParament.push_back(“14.32”);
::g_statu.param3 = RecipeJoin(listParament, pDataFormat);
return ::g_statu.param3.c_str();
}
break;
}
}
return “”;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值