Eclipse Plug-in 编辑器(五)

首先申明下,本文为笔者学习《Eclipse插件开发学习笔记》的笔记,并加入笔者自己的理解和归纳总结。

1. 创建编辑器

在【extensions】中添加【org.eclipse.ui.editors】。
在【org.eclipse.ui.editors】中添加【editor】。在【class】中指定编辑器类,必须继承IEditorPart

<extension
      point="org.eclipse.ui.editors">
    <editor
          class="com.plugin.blog.demo.edit.DemoEditorPart"
          default="false"
          icon="icons/icon_editor.png"
          id="com.plugin.blog.demo.edit.DemoEditorPart"
          name="DemoEditorPart">
    </editor>
</extension>

DemoEditorView类,继承FormEditorFormEditor是Eclipse自带的分页编辑器。

public class DemoEditorPart extends FormEditor {
    private UserInfoFormPage mInfoFormPage;
    private UserPreviewFormPage mPreviewFormPage;

    @Override
    protected void addPages() {
        mInfoFormPage = new UserInfoFormPage(this);
        mPreviewFormPage = new UserPreviewFormPage(this);
        try {
            addPage(mInfoFormPage);
            addPage(mPreviewFormPage);
        } catch (PartInitException e) {
        }

        IEditorInput input = getEditorInput();
        setPartName(input.getName());
        setTitleToolTip(input.getToolTipText());		
    }

    @Override
    public void doSave(IProgressMonitor monitor) {
    }

    @Override
    public void doSaveAs() {
    }

    @Override
    public boolean isSaveAsAllowed() {
        return false;
    }

}

2. 打开编辑器

DemoEditorInput类,继承IEditorInput,包含数据类UserInfoManager
覆盖equals()方法,避免重复打开同一个内容的编辑器。

public class DemoEditorInput implements IEditorInput {
    private User mUser;
    private UserInfoManager mManager;

    public DemoEditorInput(User user) {
        this.mUser = user;
        mManager = UserInfoManager.getInstance(mUser.getName());
    }

    @Override
    public <T> T getAdapter(Class<T> adapter) {
        return null;
    }

    @Override
    public boolean exists() {
        return false;
    }

    @Override
    public ImageDescriptor getImageDescriptor() {
        return null;
    }

    @Override
    public String getName() {
        return mUser.getName();
    }

    @Override
    public IPersistableElement getPersistable() {
        return null;
    }

    @Override
    public String getToolTipText() {
        return getName() + "信息";
    }

    public UserInfoManager getManager() {
        return mManager;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof DemoEditorInput) {
            return mUser == ((DemoEditorInput) obj).mUser;
        }
        return super.equals(obj);
    }
}

DemoViewPart视图中添加双击操作(DemoViewPart可参考Eclipse Plug-in 视图(三)),调用IWorkbenchPageopenEditor()方法打开一个编辑器,

mTableViewer.addDoubleClickListener(new IDoubleClickListener() {
    @Override
    public void doubleClick(DoubleClickEvent event) {
        ISelection selection = mTableViewer.getSelection();
        User user = (User) ((IStructuredSelection) selection).getFirstElement();
        IWorkbenchPage activePage = getSite().getWorkbenchWindow().getActivePage();
        if (activePage != null) {
            try {
                activePage.openEditor(new DemoEditorInput(user), 
                    "com.plugin.blog.demo.edit.DemoEditorPart");
            } catch (PartInitException e) {
            }
        }
    }
});

3. 数据模型

UserInfo是数据的父类,其中namedescription用来显示界面的标题和描述,getText()方法用来显示整体信息。

public abstract class UserInfo {
    private String mTitle, mDescription;
    private PropertyChangeSupport mSupport;

    public UserInfo(String title, String description) {
        this.mTitle = title;
        this.mDescription = description;
        mSupport = new PropertyChangeSupport(this);
    }

    public String getTitle() {
        return mTitle;
    }

    public String getDescription() {
        return mDescription;
    }

    public abstract String getText();

    public void addPropertyChangeListener(PropertyChangeListener listener) {
        mSupport.addPropertyChangeListener(listener);
    }

    public void removePropertyChangeListener(PropertyChangeListener listener) {
        mSupport.removePropertyChangeListener(listener);
    }

    public void firePropertyChange(String propertyName,
            Object oldValue, Object newValue) {
        mSupport.firePropertyChange(propertyName, oldValue, newValue);
    }

}

UserBasicInfo类,显示个人基本信息,包括性别和年龄。在setXX()方法中调用firePropertyChange()方法,发出修改提示。

public class UserBasicInfo extends UserInfo {
    public final static String PROPERTY_MALE = "male";
    public final static String PROPERTY_AGE = "age";

    private boolean mMale = true;
    private int mAge;

    public UserBasicInfo() {
        super("基本信息", "显示和查看基本信息");
    }

    public boolean isMale() {
        return mMale;
    }

    public void setMale(boolean male) {
        boolean oldValue = mMale;
        this.mMale = male;
        firePropertyChange(PROPERTY_MALE, oldValue, mMale);
    }

    public int getAge() {
        return mAge;
    }

    public void setAge(int age) {
        int oldValue = mAge;
        this.mAge = age;
        firePropertyChange(PROPERTY_AGE, oldValue, mAge);
    }

    @Override
    public String getText() {
        return "性别:" + (mMale ? "男" : "女") + "\n"
                + "年龄:" + (mAge > 0 ? (mAge + "岁") : "");
    }

}

UserContractInfo类,显示个人联系信息,包括手机和地址。同样在set()方法中调用firePropertyChange()方法,发出修改提示。

public class UserContractInfo extends UserInfo {
    public final static String PROPERTY_PHONE = "phone";
    public final static String PROPERTY_ADDR = "addr";

    private String mPhone;
    private String mAddress;

    public UserContractInfo() {
        super("联系信息", "查看和修改联系信息");
    }

    public String getPhone() {
        return mPhone;
    }

    public void setPhone(String phone) {
        String oldValue = mPhone;
        this.mPhone = phone;

        firePropertyChange(PROPERTY_PHONE, oldValue, mPhone);
    }

    public String getAddress() {
        return mAddress;
    }

    public void setAddress(String address) {
        String oldValue = mAddress;
        this.mAddress = address;

        firePropertyChange(PROPERTY_PHONE, oldValue, mAddress);
    }

    @Override
    public String getText() {
        return "手机号:" + ((mPhone != null) ? mPhone : "") + "\n"
                + "联系地址:" + ((mAddress != null) ? mAddress : "");
    }

}

4. 编辑器状态

UserInfoManager类用来读取和存储数据,并且监听数据的修改。
当数据被修改后,isDirty()方法返回true,调用doSave()方法后,isDirty()方法返回false

public class UserInfoManager implements PropertyChangeListener {
    private String mName;
    private boolean mDirty;
    private List<UserInfo> mUserInfoList;
    private UserBasicInfo mBasicInfo;
    private UserContractInfo mContractInfo;

    private PropertyChangeSupport mSupport;

    private UserInfoManager(String name) {
        this.mName = name;
        mUserInfoList = new ArrayList<>();
        mSupport = new PropertyChangeSupport(this);
        load();
        mDirty = false;
    }

    private void load() {
        mBasicInfo = new UserBasicInfo();
        mContractInfo = new UserContractInfo();

        mBasicInfo.addPropertyChangeListener(this);
        mUserInfoList.add(mBasicInfo);

        mContractInfo.addPropertyChangeListener(this);
        mUserInfoList.add(mContractInfo);
    }

    public Object[] toArray() {
        return mUserInfoList.toArray();
    }

    public boolean isDirty() {
        return mDirty;
    }

    public void doSave(IProgressMonitor monitor) {
        mDirty = false;
    }

    @Override
    public void propertyChange(PropertyChangeEvent evt) {
        if (!mDirty) {
            mDirty = true;
            mSupport.firePropertyChange("PROP_DIRTY", false, mDirty);	
        }
    }

    public void addPropertyChangeListener(PropertyChangeListener listener) {
        mSupport.addPropertyChangeListener(listener);
    }

    public void removePropertyChangeListener(PropertyChangeListener listener) {
        mSupport.removePropertyChangeListener(listener);
    }

    public UserInfo getUserInfo(int index) {
        if (index >= 0 && index < mUserInfoList.size()) {
            return mUserInfoList.get(index);
        }
        return null;
    }

    public static UserInfoManager getInstance(String name) {
        return new UserInfoManager(name);
    }

}

DemoEditorView类中addPages()方法,添加对UserInfoManager的监听,并使用UserInfoManager的状态控制自己的状态。

private UserInfoManager mManager;

@Override
protected void addPages() {
    mManager = ((DemoEditorInput) input).getManager();
    mManager.addPropertyChangeListener(new PropertyChangeListener() {
        @Override
        public void propertyChange(PropertyChangeEvent evt) {
            firePropertyChange(PROP_DIRTY);				
        }
    });
}

@Override
public boolean isDirty() {
    return mManager.isDirty();
}

@Override
public void doSave(IProgressMonitor monitor) {
    mManager.doSave(monitor);
    firePropertyChange(PROP_DIRTY);
}

5. 添加编辑页

UserInfoFormPage类,用来编辑数据,使用UserInfoBlock创建界面。
UserInfoBlock类继承MasterDetailsBlock,左边是列表,右边是具体信息。

public class UserInfoFormPage extends FormPage {
    private UserInfoBlock mBlock;

    public UserInfoFormPage(FormEditor formEditor) {
        super(formEditor, "com.plugin.blog.demo.edit.UserInfoFormPage", "编辑");
        mBlock = new UserInfoBlock(this);
    }

    @Override
    protected void createFormContent(IManagedForm managedForm) {
        ScrolledForm form = managedForm.getForm();
        form.setText("编辑个人信息");
        form.setBackgroundImage(ImageKeys.getImageDescriptor(
                ImageKeys.IMAGE_FORM_BANNER).createImage());

        mBlock.createContent(managedForm);
    }

    public class UserInfoBlock extends MasterDetailsBlock {
        private FormPage mFormPage;

        private AbstractDetailsPage mBasicInfoDetailPage, mContractInfoDetailsPage;

        public UserInfoBlock(FormPage formPage) {
            this.mFormPage = formPage;
            mBasicInfoDetailPage = new UserBasicInfoDetailsPage();
            mContractInfoDetailsPage = new UserContractInfoDetailsPage();
        }

        @Override
        protected void createMasterPart(IManagedForm managedForm, Composite parent) {
            FormToolkit toolkit = managedForm.getToolkit();
            Section section = toolkit.createSection(parent,
                    Section.DESCRIPTION | Section.TITLE_BAR);
            section.setText("信息列表");
            section.setDescription("包括基本信息、联系方式和其他信息");
            section.marginHeight = 5;

            Composite compUserInfo = toolkit.createComposite(section, SWT.WRAP);
            compUserInfo.setLayout(new FillLayout());

            Table table = toolkit.createTable(compUserInfo, SWT.NULL);
            toolkit.paintBordersFor(compUserInfo);
            section.setClient(compUserInfo);
            final SectionPart sectionPart = new SectionPart(section);
            managedForm.addPart(sectionPart);

            TableViewer tableViewer = new TableViewer(table);
            tableViewer.setContentProvider(new UserInfoContentProvider());
            tableViewer.setLabelProvider(new UserInfoLabelProvider());
            tableViewer.setInput(mFormPage.getEditor().getEditorInput());

            tableViewer.addSelectionChangedListener(new ISelectionChangedListener() {
                @Override
                public void selectionChanged(SelectionChangedEvent event) {
                    managedForm.fireSelectionChanged(sectionPart, event.getSelection());
                }
            });
        }

        @Override
        protected void registerPages(DetailsPart detailsPart) {
            // 和上面的managedForm.fireSelectionChanged()方法相对应
            // 最终会调用Selection.element.getClass对应的IDetailsPage
            detailsPart.registerPage(UserBasicInfo.class, mBasicInfoDetailPage);
            detailsPart.registerPage(UserContractInfo.class, mContractInfoDetailsPage);
        }

        @Override
        protected void createToolBarActions(IManagedForm managedForm) {
        }

    }

    private static class UserInfoContentProvider implements IStructuredContentProvider {
        @Override
        public Object[] getElements(Object inputElement) {
            if (inputElement instanceof DemoEditorInput) {
                return ((DemoEditorInput) inputElement).getManager().toArray();
            }
            return new Object[0];
        }
    }

    private static class UserInfoLabelProvider implements ITableLabelProvider {

        @Override
        public void addListener(ILabelProviderListener listener) {
        }

        @Override
        public void dispose() {
        }

        @Override
        public boolean isLabelProperty(Object element, String property) {
            return false;
        }

        @Override
        public void removeListener(ILabelProviderListener listener) {

        }

        @Override
        public Image getColumnImage(Object element, int columnIndex) {
            return null;
        }

        @Override
        public String getColumnText(Object element, int columnIndex) {
            if (element instanceof UserInfo) {
                return ((UserInfo) element).getTitle();
            }
            return null;
        }
    }

}

AbstractDetailsPage类,

  • selectionChanged()方法用来实现点击事件
  • createContentInfo()方法用来创建界面
  • createText()createChoice()是创建界面的公共方法,分别对其中的TextButton进行监听,并修改数据模型UserInfo
  • updateUI()是修改界面,并且不需要触发界面的监听
  • updateInfo()是修改数据模型
public abstract class AbstractDetailsPage implements IDetailsPage {
    private IManagedForm mForm;
    protected Section mSection;
    private UserInfo mUserInfo;
    private boolean mRefresh;

    @Override
    public void initialize(IManagedForm form) {
        this.mForm = form;
    }

    @Override
    public void dispose() {
    }

    @Override
    public boolean isDirty() {
        return false;
    }

    @Override
    public void commit(boolean onSave) {
    }

    @Override
    public boolean setFormInput(Object input) {
        return false;
    }

    @Override
    public void setFocus() {
    }

    @Override
    public boolean isStale() {
        return false;
    }

    @Override
    public void refresh() {
        mRefresh = true;
        updateUI(mUserInfo);
        mRefresh = false;
    }

    @Override
    public void selectionChanged(IFormPart part, ISelection selection) {
        if (selection != null && !selection.isEmpty()) {
            mUserInfo = (UserInfo) ((IStructuredSelection) selection).getFirstElement();
            mSection.setText(mUserInfo.getTitle());
            mSection.setDescription(mUserInfo.getDescription());

            mRefresh = true;
            updateUI(mUserInfo);
            mRefresh = false;
        }
    }

    protected void updateUI(UserInfo userInfo) {
    }

    @Override
    public void createContents(Composite parent) {
        parent.setLayout(new TableWrapLayout());

        FormToolkit toolkit = mForm.getToolkit();
        mSection = toolkit.createSection(parent, Section.DESCRIPTION|Section.TITLE_BAR);
        TableWrapData td = new TableWrapData(TableWrapData.FILL, TableWrapData.TOP);
        td.grabHorizontal = true;
        mSection.setLayoutData(td);

        Composite compParent = toolkit.createComposite(mSection, SWT.NONE);
        GridLayout layout = new GridLayout();
        layout.numColumns = 2;
        compParent.setLayout(layout);

        mSection.setClient(compParent);

        createContentInfo(mForm, compParent);
    }

    protected abstract void createContentInfo(IManagedForm form, Composite parent);

    protected Text createText(IManagedForm form, Composite parent, String name) {
        Label label = form.getToolkit().createLabel(parent, name);
        GridData labelGd = new GridData(SWT.FILL, SWT.CENTER, false, false);
        labelGd.widthHint = 60;
        label.setLayoutData(labelGd);

        Text text = form.getToolkit().createText(parent, "");
        GridData textGd = new GridData(SWT.FILL, SWT.CENTER, true, false);
        text.setLayoutData(textGd);
        text.addModifyListener(new ModifyListener() {
            @Override
            public void modifyText(ModifyEvent e) {
                if (!mRefresh) {
                    updateInfo(mUserInfo);
                }
            }
        });

        return text;
    }

    protected List<Button> createChoice(IManagedForm form, Composite parent,
                    String[] array, int selectIndex) {
        Composite choiceComp = form.getToolkit().createComposite(parent);
        GridData choiceCompGd = new GridData(SWT.FILL, SWT.CENTER, true, false, 2, 1);
        choiceComp.setLayoutData(choiceCompGd);

        GridLayout choiceCompLayout = new GridLayout(array.length, false);
        choiceComp.setLayout(choiceCompLayout);

        List<Button> btnList = new ArrayList<>();
        for (String str : array) {
            Button btn = form.getToolkit().createButton(choiceComp, str, SWT.RADIO);
            btn.setText(str);
            GridData btnGd = new GridData(SWT.FILL, SWT.CENTER, false, false);
            btn.setLayoutData(btnGd);
            btn.addSelectionListener(new SelectionAdapter() {
                @Override
                public void widgetSelected(SelectionEvent e) {
                    if (!mRefresh) {
                        updateInfo(mUserInfo);
                    }
                }
            });

            btnList.add(btn);
        }
        btnList.get(selectIndex).setSelection(true);
        return btnList;
    }

    protected void updateInfo(UserInfo userInfo) {
    }

}

UserBasicInfoDetailsPage

public class UserBasicInfoDetailsPage extends AbstractDetailsPage {
    private List<Button> mSexBtnList;
    private Text mText;

    @Override
    protected void createContentInfo(IManagedForm form, Composite parent) {
        FormToolkit toolkit = form.getToolkit();
        Label sexLabel = toolkit.createLabel(parent, "性别:");
        GridData sexGd = new GridData(SWT.FILL, SWT.CENTER, true, false, 2, 1); 
        sexLabel.setLayoutData(sexGd);

        mSexBtnList = createChoice(form, parent, new String[]{"男", "女"}, 0);
        mText = createText(form, parent, "年龄");
    }

    @Override
    protected void updateUI(UserInfo userInfo) {
        if (userInfo == null) {
            mSexBtnList.get(0).setSelection(true);
            mSexBtnList.get(1).setSelection(false);
            mText.setText("");
        } else {
            UserBasicInfo userBasicInfo = (UserBasicInfo) userInfo;

            boolean male = userBasicInfo.isMale();
            mSexBtnList.get(0).setSelection(male);
            mSexBtnList.get(1).setSelection(!male);

            if (userBasicInfo.getAge() <= 0) {
                mText.setText("");
            } else {
                mText.setText(Integer.toString(userBasicInfo.getAge()));
            }
        }
    }

    private int parseInteger(String text) {
        try {
            return Integer.parseInt(text);
        } catch (NumberFormatException e) {
        }
        return 0;
    }

    @Override
    protected void updateInfo(UserInfo userInfo) {
        super.updateInfo(userInfo);
        UserBasicInfo userBasicInfo = (UserBasicInfo) userInfo;
        if (mSexBtnList.get(0).getSelection()) {
            userBasicInfo.setMale(true);
        } else if (mSexBtnList.get(1).getSelection()) {
            userBasicInfo.setMale(false);
        }
        userBasicInfo.setAge(parseInteger(mText.getText()));
    }

}

显示如下
在这里插入图片描述
UserContractInfoDetailsPage

public class UserContractInfoDetailsPage extends AbstractDetailsPage {
    private Text mPhoneText;
    private Text mAddressText;

    @Override
    protected void createContentInfo(IManagedForm form, Composite parent) {
        mPhoneText = createText(form, parent, "手机号");
        mAddressText = createText(form, parent, "家庭地址");
    }

    @Override
    protected void updateUI(UserInfo userInfo) {
        if (userInfo == null) {
            mPhoneText.setText("");
            mAddressText.setText("");
        } else {
            UserContractInfo userContractInfo = (UserContractInfo) userInfo;

            mPhoneText.setText(userContractInfo.getPhone() == null ?
                        "" : userContractInfo.getPhone());
            mAddressText.setText(userContractInfo.getAddress() == null ?
                        "" : userContractInfo.getAddress());
        }
    }

    @Override
    protected void updateInfo(UserInfo userInfo) {
        super.updateInfo(userInfo);

        UserContractInfo userContractInfo = (UserContractInfo) userInfo;
        userContractInfo.setPhone(mPhoneText.getText());
        userContractInfo.setAddress(mAddressText.getText());		
    }

}

显示如下
在这里插入图片描述
UserPreviewFormPage类显示预览信息

public class UserPreviewFormPage extends FormPage 
            implements PropertyChangeListener {
    private CTabFolder mTabFolder;
    private Text mText;
    private UserInfoManager mManager;

    public UserPreviewFormPage(FormEditor editor) {
        super(editor, "com.plugin.blog.demo.edit.UserPreviewFormPage", "预览");
    }

    @Override
    protected void createFormContent(IManagedForm managedForm) {
        ScrolledForm form = managedForm.getForm();
        form.setText("信息预览");
        form.setBackgroundImage(ImageKeys.getImageDescriptor(
                ImageKeys.IMAGE_FORM_BANNER).createImage());
        FormToolkit toolkit = managedForm.getToolkit();

        Composite parentComp = form.getBody();
        parentComp.setLayout(new GridLayout());

        mTabFolder = new CTabFolder(parentComp, SWT.FLAT | SWT.TOP);
        toolkit.adapt(mTabFolder, true, true);
        GridData gd = new GridData(GridData.FILL_HORIZONTAL);
        gd.heightHint = 0;
        mTabFolder.setLayoutData(gd);

        toolkit.paintBordersFor(mTabFolder);

        createTitle();
        createContent(toolkit, parentComp);
        mTabFolder.setSelection(0);

        mTabFolder.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                updateSelection();
            }
        });

        updateSelection();
    }

    private void createTitle() {
        mManager = ((DemoEditorInput) getEditor().getEditorInput()).getManager();
        Object[] elementArray = mManager.toArray();

        for (Object element : elementArray) {
            CTabItem item = new CTabItem(mTabFolder, SWT.NULL);
            UserInfo userInfo = (UserInfo) element;
            item.setText(userInfo.getTitle());
            userInfo.addPropertyChangeListener(this);
        }
    }

    // 数据模型修改后,刷新当前界面
    @Override
    public void propertyChange(PropertyChangeEvent evt) {
        updateSelection();
    }

    private void createContent(FormToolkit toolkit, Composite parent) {
        Composite tabContentComp = toolkit.createComposite(parent);
        tabContentComp.setLayoutData(new GridData(GridData.FILL_BOTH));
        tabContentComp.setLayout(new GridLayout());

        mText = toolkit.createText(tabContentComp, "", SWT.MULTI | SWT.WRAP);
        mText.setLayoutData(new GridData(GridData.FILL_BOTH));

        mText.setEditable(false);
    }

    private void updateSelection() {
        if (mTabFolder == null)
            return;
        int index = mTabFolder.getSelectionIndex();
        mText.setText(mManager.getUserInfo(index).getText());
    }

}

显示如下
在这里插入图片描述 在这里插入图片描述

6. 文件存储

XMLMemento可以实现XML文件的存储。在UserInfoManagerload()doSave()方法中加入文件存储。

private void load() {
    mBasicInfo = new UserBasicInfo();
    mContractInfo = new UserContractInfo();

    try {
        XMLMemento memento = XMLMemento.createReadRoot(new FileReader(getStoreFile()));
        IMemento child = memento.getChild("basic_info");
        mBasicInfo.setMale(child.getBoolean("male"));
        mBasicInfo.setAge(child.getInteger("age"));

        child = memento.getChild("contract_info");
        mContractInfo.setPhone(child.getString("phone"));
        mContractInfo.setAddress(child.getString("address"));
    } catch (Exception e) {		
    }

    mBasicInfo.addPropertyChangeListener(this);
    mUserInfoList.add(mBasicInfo);

    mContractInfo.addPropertyChangeListener(this);
    mUserInfoList.add(mContractInfo);
}

public void doSave(IProgressMonitor monitor) {
    XMLMemento memento = XMLMemento.createWriteRoot("user");
    IMemento child = memento.createChild("basic_info");
    child.putBoolean("male", mBasicInfo.isMale());
    child.putInteger("age", mBasicInfo.getAge());

    child = memento.createChild("contract_info");
    child.putString("phone", mContractInfo.getPhone());
    child.putString("address", mContractInfo.getAddress());

    try {
        memento.save(new FileWriter(getStoreFile()));
    } catch (IOException e) {
        e.printStackTrace();
    }
    mDirty = false;
}

private File getStoreFile() {
    return Activator.getDefault().getStateLocation()
            .append("user_" + mName + ".xml").toFile();
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值