请选择 进入手机版 | 继续访问电脑版

库管易

 找回密码
 立即注册

QQ登录

只需一步,快速开始

扫描二维码登录本站

查看: 52544|回复: 19

使用C++开发Excel插件(5):编写完整的Xll外接程序

[复制链接]
  一、认识xll
  Xll是一个标准的win32 dll,但它导出了一些特殊的函数,这些函数可以被Excel调用,也可以把自定义的函数注册到Excel中,以扩展Excel的功能。通过xll你可以在Excel中添加菜单,工具按钮等等。同样你可以把你编写的xll发布给其他人共享。
  如果你现在准备编写一个xll插件的话,你首先需要准备一些必备的资源,如xlcall32.dll以及它的导出库文件xlcall32.lib,函数的定义文件xlcall.h。至于如何获得这些资源,你可以参考第一章的内容。在编写xll时,你需要配置工程以使你编译器可以找到这些文件。在Visual C++6中菜单Project->settings在项目设置对话框中的Link页中加入xlcall32.lib,注意xlcall32.lib文件必须放在编译可以找到的位置,你可以把它放在项目当前目录中。
  

635344729887988281.jpg

635344729887988281.jpg


  在Visual studio .Net 2003中同样需要设置,你可以通过点击菜单项目—>属性,在链接器—>输入页中加入到入库。
  

635344729915312500.jpg

635344729915312500.jpg


  二、如何加载宏
  Xll宏的加载有多种方式,一种是通过Excel的加载宏管理器来加载一个可用的xll宏。另一种方式是通过在xll宏中实现自动加载函数,并把xll文件复制到安装目录下的“XLSTART”文件夹中,或放在C:/Documents and Settings/wxy/Application Data/Microsoft/AddIns目录下,当Excel启动时会自动加载这两个文件夹中的xll宏。你也可以像打开文件一样通过Excel的打开文件命令打开xll文件。几种方式实现的效果基本差不多,被加载的xll宏中的函数,方法等都可以在Excel中调用。
  通过加载宏管理器加载xll文件,你需要在Excel中点击菜单工具->加载宏,在加载宏管理窗口中通过浏览按钮打开你的xll文件,此时,加载宏管理器会把xll文件注册到Excel中,并把注册名列在管理器中。注册名是xll中的函数“xlAddInManagerInfo”中设定的用于标识此xll的字符串。例如下图中的“My Add-in!!!!”是我们刚刚添加的xll宏。
  

635344729921083984.jpg

635344729921083984.jpg


  刚刚被加载的宏默认是被激活的。关于激活状态我们将在后面讲解。这时我们就可以在Excel中调用刚刚加载的xll宏中函数和方法了。注意使用加载文件时必须是包含Excel加载函数的文件,否则Excel会提示为无效加载宏:
  

635344729924960937.jpg

635344729924960937.jpg

  如果使用自动加载,你需要把xll文件放置到“C:/Documents and Settings/wxy/Application Data/Microsoft/AddIns”目录下,或者放到office的安装目录下的“XLSTART”目录中,如果没有此目录,你可以新建一个。但“XLSTART”目录的位置会因office的版本不同而不同,在office2003中它的路径是“安装目录/Microsoft Office/OFFICE11/XLSTART”。使用自动加载的xll宏并不显示在加载宏管理器中。当然它必须也是有效的加载宏。当你启动Excel时,Excel会自动扫描这些文件夹,并加载文件夹中所有可用的加载宏文件。
  当你加载一个xll加载宏时,Excel主要执行以下操作:
  调用xlAddInManagerInfo注册加载宏的名称。
  调用xlAutoOpen初始化xll并注册自定义函数,添加菜单、按钮等。

  三、激活加载宏
  我们在前面看到,当一个xll加载宏第一次被宏管理器加载时,默认是选中的,这表明这个宏此时是被激活的,xll宏一旦被激活就意味着它的函数或方法可以被调用了,此时我们打开插入函数窗口(菜单插入->函数),在其中我们就可以找到我们自定义的函数类别及这个类别下的所有函数。
  

635344729928808593.jpg

635344729928808593.jpg


  如果你不想要这些函数执行,那么你可以取消加载宏管理器中的对应选项,这时对应的xll宏就被取消了激活状态,Excel中的所有对相关函数的引用都将不可用,虽然你取消了激活状态,但有时还会在插入函数对话框中仍然看到这个xll文件中的函数,但这并不代表函数仍然能用,因为如果你此时调用函数,Excel不会让你输入任何参数。通常当你使用的函数名在不同xll加载宏中同时存在时,取消其它发生冲突的xll宏就显得非常重要了。
  

635344729932080078.jpg

635344729932080078.jpg

  当你要激活一个xll加载宏,Excel执行以下操作:
  调用xlAutoOpen初始化xll并添加菜单、按钮,注册函数等。

  四、删除加载宏
  当使用加载宏管理器加载xll宏时,Excel会把xll文件的路径保存在注册表中,在Excel下次启动时Excel会按照这个路径寻找对应的文件,但是如果对应的动态链接库已被删除或路径发生变化,Excel在启动时会提示文件找不到,但此时Excel并不会删除其在Excel加载宏管理器中的对应项。直到你打开加载宏管理器并改变其激活状态时才提示是否要删除未找到的项,我不是很理解Excel为什么要这么设计。如果在加载宏管理器里有个删除按钮,我觉得会好用许多。

  五、创建加载宏
  从前面已经看到,并不是所有的动态链接库都可以做为Excel加载宏使用,那么Excel是如何判别一个动态链接库是不是加载宏呢,又是如何取得加载宏内部的自定义函数并调用它呢。
  Excel加载宏xll中必须实现一些特定的导出函数,通过这些函数Excel获得xll的信息并把xll文件加载到Excel中,同样Excel通过这些特定的函数关闭或移除xll加载宏。Xll也是通过这些特定的函数与Excel交互,并把自定义的函数和方法注册进Excel中。其中一些函数是实现xll加载宏必不可少的,Excel也是通过查找这些函数来判断一个动态链接库是否是可用的xll加载宏。下面列出了创建一个xll加载宏常要实现并导出的几个特定函数:
  xlAutoOpen
  xlAutoClose
  xlAutoAdd
  xlAutoRemove
  xlAddInManagerInfo
  xlAutoFree
  注意,这些函数并不区分大小写,其中xlAutoOpen,xlAutoClose和xlAddInManagerInfo是必不可少的三个导出函数。

  通过名字名字并不能看出Excel是何时调用哪个函数,下面我们将通过一个表格列出这些函数的调用次序,以及调用的时间:
  
执行状态这时调用的函数
打开加载宏管理器xlAddInManagerInfo
在加载宏管理器中选中加载宏,即激活加载宏xlAutoAdd
  xlAutoOpen
在加载宏管理器中取消选中加载宏,即取消激活加载宏xlAutoRemove
  xlAutoClose
第一次载入加载宏xlAddInManagerInfo
  xlAutoAdd
  xlAutoOpen
启动Excel时,并且xll宏在之前以被加载宏管理器加载xlAutoOpen
关闭Excel,但此时xll宏并没有被激活不执行任何函数
关闭Excel,并且xll宏是被激活的xlAutoClose
  xlAddInManagerInfo
关闭Excel,但当提示是否保存时,选择了取消按钮xlAutoClose
  

  通过上面的表格我们知道了Excel在操作xll加载宏是所需调用执行的函数,接下来我们将逐一介绍如何实现这些函数。
回复

使用道具 举报

 楼主| 显示全部楼层
  六、最基本的加载宏函数
  在这一节中我们将逐一介绍前面提到的几个最基本的函数,了解了这些函数的用法,就可以编写一个简单的xll加载宏了。每个函数都会有一个例子代码,这些代码是来自“ExcelAddin”工程中,你可以在光盘上找到“ExcelAddin”工程。

  1、xlAutoOpen
  函数原形:int __stdcall xlAutoOpen(void)
  当xll加载宏被加载到Excel中时调用这个函数。在这个函数中加入任何需要初始化的操作,比如,注册函数,添加菜单、按钮等等。如果函数执行成功返回整数1。为了更好的阅读程序可以定义一个宏或常量表示函数是否执行成功。
  下面的例子函数在执行时会在Excel中注册一个“MyFunc”函数和一个“MyMethod”方法,并在Excel中添加一个菜单,如果函数执行成功,你就可以在Excel调用“MyFunc”函数和“MyMethod”方法,并可以看到Excel界面上增加一个“MyMenu”的菜单。
  1. int __stdcall xlAutoOpen(void)
  2. {
  3.        MessageBox(NULL,_T("xlAutoOpen"),_T("xll"),MB_OK);
  4.        if (true == m_Initialize)
  5.        {
  6.               return(XLL_SUCCESS);
  7.        }

  8.        int iNum = 0;
  9.        static int iErr = 0;
  10.        //从新设置字符串
  11.        for (iNum=0;iNum<9;iNum++)
  12.        {
  13.               func[iNum][0] = (BYTE)(strlen(func[iNum])-1);
  14.        }

  15.        for (iNum=0;iNum<10;iNum++)
  16.        {
  17.               menu[iNum][0] = (BYTE)(strlen(menu[iNum])-1);
  18.        }

  19.        for (iNum=0;iNum<9;iNum++)
  20.        {
  21.               method[iNum][0] = (BYTE)(strlen(method[iNum])-1);
  22.        }


  23.        //获得xll的名字
  24.        static XLOPER xllName;
  25.        Excel4(xlGetName,&xllName,0);

  26.        //注册“MyFunc”函数
  27.        iErr = Excel4(xlfRegister,0,10,(LPXLOPER)&xllName,
  28.               (LPXLOPER)TempStr(func[0]),
  29.               (LPXLOPER)TempStr(func[1]),
  30.               (LPXLOPER)TempStr(func[2]),
  31.               (LPXLOPER)TempStr(func[3]),
  32.               (LPXLOPER)TempStr(func[4]),
  33.               (LPXLOPER)TempStr(func[5]),
  34.               (LPXLOPER)TempStr(func[6]),
  35.               (LPXLOPER)TempStr(func[7]),
  36.               (LPXLOPER)TempStr(func[8]),
  37.               (LPXLOPER)TempStr(func[9])
  38.               );

  39.        if (iErr != xlretSuccess)
  40.        {
  41.               MessageBox(NULL,"Can not register fun!","",MB_OK);
  42.               return(XLL_FAIL);
  43.        }

  44.        //注册“MyMethod”方法
  45.        iErr = Excel4(xlfRegister,0,6,(LPXLOPER)&xllName,
  46.               (LPXLOPER)TempStr(method[0]),
  47.               (LPXLOPER)TempStr(method[1]),
  48.               (LPXLOPER)TempStr(method[2]),
  49.               (LPXLOPER)TempStr(method[3]),
  50.               (LPXLOPER)TempStr(method[4]),
  51.               (LPXLOPER)TempStr(method[5])
  52.               );

  53.        if (iErr != xlretSuccess)
  54.        {
  55.               MessageBox(NULL,"Can not register Method!","",MB_OK);
  56.               return(XLL_FAIL);
  57.        } 

  58.        //释放xllName所占用的空间
  59.        Excel4(xlFree,0,1,(LPXLOPER)&xllName); 

  60.        nMenuItems = 2;
  61.        static XLOPER xMenu;
  62.        static XLOPER xMenuList[10*5];

  63.        //设置菜单参数
  64.        xMenu.xltype = xltypeMulti;
  65.        xMenu.val.array.lparray = &xMenuList[0];
  66.        xMenu.val.array.rows = nMenuItems;
  67.        xMenu.val.array.columns = 5;

  68.        for (iNum=0;iNum<10;iNum++)
  69.        {
  70.               xMenuList[iNum].xltype = xltypeStr;
  71.               xMenuList[iNum].val.str = menu[iNum];
  72.        }

  73.        //注册菜单
  74.        Excel4(xlfAddMenu,0,2,TempNum(1),(LPXLOPER)&xMenu);

  75.        m_Initialize = true;

  76.        return(XLL_SUCCESS);
  77. }
复制代码

  2、xlAutoClose
  函数原形是:int __stdcall xlAutoClose(void)
  Excel会在关闭或卸载xll加载宏时调用此函数。当执行这个函数时意味着你要关闭xll加载宏并且不再容许Excel调用这个加载宏中的函数或方法,所以你可以在这个函数中实现释放xll加载宏占用的内存以及注销自定义函数。你也可以在这个函数执行时删除菜单,按钮等。
  下面的例子函数在执行时会把“MyMenu”菜单项从Excel中移除。
  1. int __stdcall xlAutoClose(void)
  2. {
  3.        if (false == m_Initialize)
  4.        {
  5.               return(XLL_SUCCESS);
  6.        }
  7.        //删除菜单
  8.        char chMenu[] = " MyMenu";
  9.        Excel4(xlfDeleteMenu,0,2,TempNum(1),TempStr(chMenu));

  10.        m_Initialize = true;

  11.        return(XLL_SUCCESS);
  12. }
复制代码

  3、xlAutoAdd
  函数原形是:int __stdcall xlAutoAdd(void)
  当通过加载宏管理器来加载xll宏和在加载宏管理器中激活xll宏时会调用此函数,但是,调用此函数的时候也会调用xlAutoOpen函数,所以你可以忽略此函数并把相关操作放在xlAutoOpen函数中执行。
  下面的例子函数通过调用xlAutoOpen执行初始化操作。
  1. int __stdcall xlAutoAdd(void)
  2. {
  3.        if (false == m_Initialize)
  4.        {
  5.               xlAutoOpen();
  6.        }

  7.        return(XLL_SUCCESS);
  8. }
复制代码

  4、xlAutoRemove
  当xll加载宏被关闭或被取消激活状态时,Excel会调用此函数,但是这个函数同样也可以被忽略,因为在调用它的时候,也会调用xlAutoClose函数。
  下面的例子函数在被调用时除显示一个提示对话框外不执行任何其他操作
  1. int __stdcall xlAutoRemove(void)
  2. {
  3.        MessageBox(NULL,"xlAutoRemove","XLL",MB_OK);

  4.        return(XLL_SUCCESS);
  5. }
复制代码

  5、xlAddInManagerInfo
  函数原形是:LPXLOPER __stdcall xlAddInManagerInfo(LPXLOPER xAction)
  Excel会在第一次打开加载宏管理器时调用此函数,从中获得有关xll文件的信息并把注册名显示在加载宏管理器中,如果xll加载宏中并没有显示注册xll加载宏名,则在加载宏管理器中显示它的文件名(不包括扩展名)。函数返回一个字符串xloper的指针,其中字符串是要注册的名字。
  下面的函数显示的注册了当前的xll加载宏的名字为“My Add-in!!!!”。
  1. LPXLOPER __stdcall xlAddInManagerInfo(LPXLOPER xAction)
  2. {
  3.        static XLOPER xInfo,xIntAction;

  4.        //find out what action must be taken
  5.        Excel4(xlCoerce,&xIntAction,2,xAction,TempInt(xltypeInt));

  6.        strcpy(xllName," My Add-in!!!!");

  7.        //Set title if asked
  8.        if (xIntAction.val.w == 1)
  9.        {
  10.               xInfo.xltype = xltypeStr;
  11.               xInfo.val.str = xllName;
  12.               xInfo.val.str[0] = (char)strlen(&xInfo.val.str[1]);
  13.        }
  14.        else
  15.        {
  16.               xInfo.xltype = xltypeErr;
  17.               xInfo.val.err = xlerrValue;
  18.        }

  19. // Excel4(xlFree,0,1,(LPXLOPER)&xInfo);

  20.        return((LPXLOPER)&xInfo);
  21. }
复制代码
  如果这个函数执行成功则在加载宏管理器中出现一项“My Add-in!!!!”。如图
  

635344729934208984.jpg

635344729934208984.jpg


  6、xlAutoFree
  函数原形是:void __stdcall xlAutoFree(xloper *)
  当xll加载宏中的一个函数返回给Excel一个指向xloper的指针时,它被调用并把xloper指针传作为参数传递给它。通过它释放xll动态分配的xloper变量所占用的内存。通常一个函数需要返回xloper指针时,那么指针所占用的内存不能在返回前被是否,否则返回的指针将指向无效地址。但是已经分配的动态内存必须在合适的时候被释放,否则就会造成内存泄露。我们可以给要释放的xloper变量的中设置xlbitDllFree状态位并通过xlAutoFree函数来解决这个问题。
  下面的例子函数功能是释放一个xloper指针,这个指针所指向的xloper可能是字符串,数组,或是一个范围的应用。函数分别对不同类型的xloper执行释放内存的操作。
  1. void __stdcall xlAutoFree(XLOPER *pXloper)
  2. {
  3.        if (pXloper->xltype & xltypeMulti)
  4.        {
  5.               int size = pXloper->val.array.rows * pXloper->val.array.columns;
  6.               XLOPER *p = pXloper->val.array.lparray;
  7.               for (;size-- > 0;p++)
  8.               {
  9.                      if (p->xltype & (xlbitDLLFree | xlbitXLFree))
  10.                      {
  11.                             xlAutoFree(p);
  12.                      }
  13.               }

  14.               if (pXloper->xltype & xlbitDLLFree)
  15.               {
  16.                      Excel4(xlFree,0,1,pXloper->val.array.lparray);
  17.               }
  18.        }
  19.        else if (pXloper->xltype == (xltypeStr | xlbitDLLFree))
  20.        {
  21.               Excel4(xlFree,0,1,pXloper->val.str);
  22.        }
  23.        else if (pXloper->xltype == (xltypeRef | xlbitDLLFree))
  24.        {

  25.        }
  26.        else if (pXloper->xltype | xlbitXLFree)
  27.        {
  28.               Excel4(xlFree,0,1,pXloper);
  29.        }
  30. }
复制代码
回复 支持 反对

使用道具 举报

 楼主| 显示全部楼层
  七、创建一个完整的xll加载宏
  你已经初步了解了一个简单xll加载宏所应该包含的内容,即上一节所介绍的函数。现在我们就来创建一个最简单的xll加载宏,其实就是把上面所讲的代码编译成xll加载宏。
  首先创建一个空win32 dll工程“ExcelAddin”,创建方法可以参考第3章的第4节“编写一个简单的动态链接库”
  复制xlcall.h和xlcall32.lib文件到工程目录下面,并把xlcall.h文件加入到工程中,在工程链接属性中加入xlcall32.lib,你可以通过菜单project->setting打开工程配置对话框,然后在Link属性页中的“Object/library modules:”项中加入xlcall32.lib名称。
  

635344729938261718.jpg

635344729938261718.jpg


  创建ExcelAddin.h文件,在这个文件中加入全局变量以及对函数的定义。下面是对这个文件中一些主要的代码的说明。
  首先定义一些程序中经常用到的常量和缓存区
  1. const int XLL_SUCCESS = 1; //定义函数执行成功
  2. const int XLL_FAIL = 0; //定义函数执行失败

  3. #define MEMORYSIZE 1024 //定义缓存区大小
  4. char m_memBuffer[MEMORYSIZE]; //自定义一个缓存区
  5. int m_memOffset = 0; // Offset of next memory block to allocate

  6. static string m_strMsg; //定义保存提示字符串的变量

  7. static char xllName[20]; //保存此xll在Excel中注册名
复制代码
  由于注册一个函数需要的参数比较多,所以我们把这些参数保存在一个二维数组中,注意所有的字符串前面都应留出一个空格,这是因为这个位置需要保存字符串的长度。
  1. static char func[10][20] = { 
  2.        " MyFunc", //xll中函数名 
  3.        " ", //参数描述符 
  4.        " MyFunc", //在Excel中的显示的名字 
  5.        " 参数1,参数2", //参数提示字符串 
  6.        " 1", //宏类型,1为函数,2为命令 
  7.        " MyCat", //此函数所在的类别 
  8.        " ", //快捷键,只有在命令类型才可用 
  9.        " ", //帮助的引用字符串 
  10.        " ", //函数所在帮组的描述字符串的编号 
  11.        " " //第一个参数的描述,此后的其他参数顺序为第二个,第三个,依次类推
  12. };
复制代码
  定义一个方法,各个参数的含义和定义函数的参数意义一样,只是在宏类型位置处的值为2,即为方法宏。
  1. static char method[6][20] = { 
  2.         " MyMethod", 
  3.         " ", 
  4.         " MyMethod", 
  5.         " ", 
  6.         " 2", 
  7.         " " 
  8.  };
复制代码
  定义一个菜单:
  1. static char menu[10][30] = { 
  2.         " &MyMenu", 
  3.         " ", 
  4.         " ", 
  5.         " wxy's xll", 
  6.         " ", 
  7.         " &MySubMenu", 
  8.         " MyMethod", 
  9.         " ", 
  10.         " Message of the dat!", 
  11.         " "
  12. };
复制代码
  定义函数:
  1. //定义xll与Excel的接口函数
  2. int __stdcall xlAutoOpen(void); //初始化xll,并注册函数,方法等
  3. int __stdcall xlAutoAdd(void);
  4. int __stdcall xlAutoClose(void); //关闭xll,
  5. int __stdcall xlAutoRemove(void);
  6. LPXLOPER __stdcall xlAddInManagerInfo(LPXLOPER xAction);
  7. void __stdcall xlAutoFree(XLOPER *pXloper);

  8. //自定义函数及方法
  9. int __stdcall MyMethod();
  10. long __stdcall MyFunc(long parm1,long parm2);

  11. LPSTR GetTempMemory(int iBytes); //获得指定数量的缓存区的指针

  12. //获得含有指定值的xloper变量指针
  13. LPXLOPER TempNum(double d);
  14. LPXLOPER TempStr(LPSTR lpstr);
  15. LPXLOPER TempInt(short int i);
复制代码
  创建ExcelAddin.cpp文件,在这个文件中加入对ExcelAddin.h文件中函数的实现代码。对于xlAutoOpen,xlAutoClose等的代码我们已在上一节中介绍过,这里就不再重复。这里需要实现自定义的函数“MyFunc”和自定义的方法“MyMethod”
  MyFunc函数在被调用时通过一个对话框显示了传递的参数,并返回两个参数的乘积。
  1. long __stdcall MyFunc(long parm1,long parm2)
  2. {
  3.        static char chTemp[100];
  4.        sprintf(chTemp,"%ld and %ld",parm1,parm2);
  5.        m_strMsg = _T("parameter is ");
  6.        m_strMsg += chTemp;
  7.        MessageBox(NULL,m_strMsg.c_str(),"MyFunc",MB_OK);

  8.        return (parm1 * parm2);
  9. }
复制代码
  MyMethod方法在被调用时显示一个字符串表示它正在被调用。
  int __stdcall MyMethod()
  {
  m_strMsg=_T(“this is a command”);
  MessageBox(NULL,m_strMsg.c_str(),_T(“MyMethod”),MB_OK);
  return(0);
  }
  在这个文件中,你还会看到其它几个函数GetTempMemory,TempNum,TempStr,TempInt,这几个函数都是用来操作缓存区的,我们将在以后章节中介绍,你可以暂时不必看它们。
  为了不让编译器修饰函数名,我们需要使用.def文件导出Excel所需的函数名。创建一个ExcelAddin.def文件并在文件中加入如下代码:
  1. LIBRARY "ExcelAddin"
  2. DESCRIPTION 'ExcelAddin Windows Dynamic Link Library'

  3. EXPORTS
  4.     ; Explicit exports can go here
  5.        xlAutoOpen
  6.        xlAutoAdd
  7.        xlAutoClose
  8.        xlAutoRemove
  9.        xlAddInManagerInfo
  10.        xlAutoFree
  11.        MyMethod
  12.        MyFunc
复制代码

  在工程更改输出文件的名称,使编译器直接输出xll文件,然后在可执行文件设置项里填入你的Excel的全路径,并在下面的参数列表栏里填入你的xll文件路径,如图所示:
  

635344729943691406.jpg

635344729943691406.jpg


  一切就绪,按F7编译,如果顺利在输出目录里就会看到一个xll文件。
  最后按F5执行Excel,由于我们把xll文件设置成了参数,此时Excel启动时会同时加载我们的xll文件,如果程序执行顺利,则会在启动后的Excel中看到我们设置的菜单“MyMenu”。执行菜单下的子菜单“MySubMenu”命令。如果弹出如下对话框则表明“MyMethod”方法注册成功了。
  

635344729946562500.jpg

635344729946562500.jpg

  再使用插入函数对话框插入我们自定义的函数,如果函数执行成功,那么恭喜你,你已经创建了一个完整的xll加载宏。
回复 支持 反对

使用道具 举报

学习了!!!!!!!!!!
回复 支持 反对

使用道具 举报

感谢分享!
回复 支持 反对

使用道具 举报

好复杂啊,感觉
回复 支持 反对

使用道具 举报

书到用时方恨少啊
回复 支持 反对

使用道具 举报


看上去好复杂啊,感觉
回复 支持 反对

使用道具 举报

谢谢分享!!!
回复

使用道具 举报

多谢楼主分享~
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

手机版|仓库管理网

GMT+8, 2024-4-18 17:34

Powered by 库管易

KuGuanYi.Com

快速回复 返回顶部 返回列表