7fe4f761创建于 4月29日历史提交
<template>
    <div>
        <div v-if="storeConfig.disable" class="ub-alert danger">
            <i class="iconfont icon-close-o"></i>
            当前环境禁止「模块管理」相关操作
        </div>
        <div class="ub-alert warning">
            <i class="iconfont icon-warning"></i>
            为了系统和数据安全,在线 <b>安装</b>、<b>卸载</b>、<b>升级</b> 模块前请做好代码和数据备份
        </div>
        <div v-if="d" class="ub-alert warning">
            <i class="iconfont icon-warning"></i>
            您还没有登录,登录后才能从模块市场安装、升级模块
            <a href="javascript:;" @click="doMemberLoginShow()"><i class="iconfont icon-user"></i>立即登录</a>
        </div>
        <div class="tw-bg-white tw-rounded tw-relative">
            <el-tabs v-model="search.tab" style="height:45px;">
                <el-tab-pane name="store">
                    <span slot="label">
                        <i class="iconfont icon-cart"></i> 模块市场
                    </span>
                </el-tab-pane>
                <el-tab-pane name="installed">
                    <span slot="label">
                        <i class="iconfont icon-list-alt"></i> 已安装
                    </span>
                </el-tab-pane>
                <el-tab-pane name="enabled">
                    <span slot="label">
                        <i class="iconfont icon-checked"></i> 已启用
                    </span>
                </el-tab-pane>
                <el-tab-pane name="disabled">
                    <span slot="label">
                        <i class="iconfont icon-close"></i> 已禁用
                    </span>
                </el-tab-pane>
                <el-tab-pane name="local">
                    <span slot="label">
                        <i class="iconfont icon-pc"></i> 本地模块
                        <i class="iconfont icon-warning" data-tip-popover="本地存在且模块市场不存在的模块"></i>
                    </span>
                </el-tab-pane>
                <el-tab-pane name="upgradeable">
                    <span slot="label">
                        <i class="iconfont icon-direction-up"></i> 可升级
                    </span>
                </el-tab-pane>
            </el-tabs>
            <a href="javascript:;" @click="search.tab='system'"
               class="ub-text-muted tw-leading-10 tw-px-3 tw-absolute tw-right-0 tw-top-0">
                <i class="iconfont icon-cog"></i> 内置模块
            </a>
            <div class="ub-padding">
                <div class="tw-float-right">
                    <el-button round style="padding:0.3125rem 0.625rem;" :loading="memberUserLoading"
                               @click="doMemberLoginShow()">
                        <span v-if="memberUserLoading">
                            登录中
                        </span>
                        <span v-else-if="memberUser.id>0">
                            <div v-if="memberUser.avatar" class="ub-cover-1-1 tw-rounded-full"
                                 style="width:1rem;display:inline-block;vertical-align:middle;"
                                 :style="{backgroundImage:`url(${memberUser.avatar})`}"></div>
                            <i v-else class="iconfont icon-user"></i>
                            {{ memberUser.username }}
                        </span>
                        <span v-else>
                            <i class="iconfont icon-user"></i>
                            未登录
                        </span>
                    </el-button>
                </div>
                <el-radio-group v-model="search.priceType" v-if="search.tab!=='system'">
                    <el-radio-button label="all"><i class="iconfont icon-list-alt"></i> 全部</el-radio-button>
                    <el-radio-button label="free"><i class="iconfont icon-gift"></i> 免费</el-radio-button>
                    <el-radio-button label="fee"><i class="iconfont icon-cny"></i> 付费</el-radio-button>
                </el-radio-group>
                <el-checkbox v-model="search.isRecommend" border v-if="search.tab!=='system'">推荐</el-checkbox>
                <el-input prefix-icon="el-icon-search"
                          v-model="search.keywords"
                          style="width:12.5rem;"
                          placeholder="搜索模块"></el-input>
            </div>
            <div class="tw-px-2" v-if="categories.length>0 && search.tab!=='system'">
                <i class="iconfont icon-category tw-ml-1 tw-text-gray-600 tw-inline-block tw-w-4"></i>
                分类:
                <a href="javascript:;" class="tw-text-gray-500 tw-mr-1"
                   :class="{'ub-text-primary':search.categoryId===0}" @click="search.categoryId=0">
                    全部
                </a>
                <a href="javascript:;" class="tw-mr-1 tw-text-gray-500 tw-mr-1"
                   :class="{'ub-text-primary':search.categoryId===cat.id}"
                   v-for="(cat,catIndex) in categories" :key="catIndex" @click="search.categoryId=cat.id">
                    {{ cat.title }}
                </a>
            </div>
            <div class="tw-px-2 tw-pt-2" v-if="types.length>0 && search.tab!=='system'">
                <i class="iconfont icon-desktop tw-ml-1 tw-text-gray-600 tw-inline-block tw-w-4"></i>
                类型:
                <a href="javascript:;" class="tw-text-gray-500 tw-mr-1"
                   :class="{'ub-text-primary':!search.type}" @click="search.type=''">
                    全部
                </a>
                <a href="javascript:;" class="tw-mr-1 tw-text-gray-500 tw-mr-1"
                   :class="{'ub-text-primary':search.type===type.value}"
                   v-for="(type,typeIndex) in types" :key="typeIndex" @click="search.type=type.value">
                    {{ type.title }}
                </a>
            </div>
            <div class="ub-empty" v-if="loading">
                <div class="icon">
                    <i class="icon-refresh iconfont tw-animate-spin tw-inline-block tw-p-4"></i>
                </div>
                <div class="text">正在加载...</div>
            </div>
            <div class="ub-empty" v-if="!loading && !filterModules.length">
                <div class="icon">
                    <i class="iconfont icon-empty-box"></i>
                </div>
                <div class="text">暂无记录</div>
            </div>
            <div class="margin-top tw-p-3" v-if="filterModules.length>0">
                <div class="row">
                    <div v-for="(module,moduleIndex) in filterModules" class="col-md-4">
                        <div
                            class="hover:tw-shadow-lg tw-bg-white tw-mb-2 tw-p-2 tw-rounded-lg tw-shadow ub-border">
                            <div style="padding-left:7.5rem;">
                                <div class="tw-w-28 tw-float-left" style="margin-left:-7.5rem;">
                                    <a v-if="module.url"
                                       :href="module._isSystem?'javascript:;':module.url"
                                       :target="module._isSystem?'':'_blank'"
                                       class="ub-cover-3-2 tw-shadow  tw-rounded"
                                       :class="{'tw-w-24 tw-cursor-default':module._isSystem,'tw-w-24':!module._isSystem}"
                                       :style="{'background-image':'url('+module.cover+')'}"></a>
                                    <div v-else
                                         class="tw-shadow tw-w-24 tw-h-16 tw-rounded ub-text-center tw-text-white tw-bg-blue-300">
                                        <i class="iconfont icon-cube"
                                           style="font-size:2rem;line-height:4rem;"></i>
                                    </div>
                                </div>
                                <div>
                                    <a :href="module._isSystem?'javascript:;':module.url"
                                       :target="module._isSystem?'':'_blank'"
                                       :class="{'tw-cursor-default':module._isSystem}"
                                       class="tw-font-bold tw-text-gray-700 ub-text-truncate tw-block">
                                        <span v-if="module._isLocal" class="ub-tag primary sm ub-bg-a">内置</span>
                                        <span v-html="$highlight(module.title,search.keywords)"></span>
                                    </a>
                                    <div v-if="!module._isSystem">
                                        <span v-if="!module.isFee && !module._isLocal"
                                              class="ub-text-success">免费</span>
                                        <div v-if="module.isFee" class="ub-text-danger">
                                            <span v-if="module.priceYearEnable">¥{{ module.priceYear }}</span>
                                            <span v-else-if="module.priceSuperEnable">¥{{ module.priceSuper }}</span>
                                        </div>
                                    </div>
                                    <div class="tw-text-gray-400 tw-text-sm tw-mt-2"
                                         style="height:2.5rem;overflow:auto;"
                                         v-html="$highlight(module.description,search.keywords)"></div>
                                </div>
                            </div>
                            <div v-if="!module._isSystem"
                                 class="tw-border-0 tw-border-solid tw-border-t tw-border-gray-100 tw-mt-2 tw-pt-2 tw-text-gray-500">
                                <div class="tw-float-right" v-if="module._isInstalled">
                                    <a v-if="module._isInstalled && module._hasConfig" href="javascript:;"
                                       :data-tk-event="'ModuleStore,Config,'+module.name"
                                       @click="doConfig(module)">
                                        <i class="iconfont icon-cog"></i> 配置
                                    </a>
                                </div>
                                <a v-if="!module._isInstalled" href="javascript:;" @click="doInstall(module)"
                                   class="tw-mr-1" :data-tk-event="'ModuleStore,Install,'+module.name"
                                >
                                    <i class="iconfont icon-plus"></i> 安装
                                </a>
                                <el-tooltip class="item" effect="dark" content="安装其他版本" placement="top">
                                    <a v-if="!module._isInstalled" href="javascript:;" @click="doInstallVersion(module)"
                                       class="tw-mr-4 tw-text-gray-400"
                                    >
                                        <i class="iconfont icon-down"></i>
                                    </a>
                                </el-tooltip>
                                <a v-if="module._isInstalled && !module._isLocal && canUpgrade(module._localVersion,module.latestVersion)"
                                   @click="doUpgrade(module)"
                                   :data-tk-event="'ModuleStore,Upgrade,'+module.name"
                                   href="javascript:;" class="ub-text-warning tw-mr-4">
                                    <i class="iconfont icon-direction-up"></i> 升级
                                </a>
                                <a v-if="module._isInstalled && module._isEnabled" href="javascript:;"
                                   @click="doDisable(module)"
                                   :data-tk-event="'ModuleStore,Disable,'+module.name"
                                   class="ub-text-danger tw-mr-4">
                                    <i class="iconfont icon-pause"></i> 禁用
                                </a>
                                <a v-if="module._isInstalled && !module._isEnabled" href="javascript:;"
                                   class="tw-mr-4"
                                   :data-tk-event="'ModuleStore,Enable,'+module.name"
                                   @click="doEnable(module)"
                                >
                                    <i class="iconfont icon-play"></i> 启用
                                </a>
                                <a v-if="module._isInstalled && !module._isEnabled" href="javascript:;"
                                   :data-tk-event="'ModuleStore,Uninstall,'+module.name"
                                   @click="doUninstall(module)"
                                   class="ub-text-danger tw-mr-4">
                                    <i class="iconfont icon-trash"></i> 卸载
                                </a>
                            </div>
                            <div v-else
                                 class="tw-border-0 tw-border-solid tw-border-t tw-border-gray-100 tw-mt-2 tw-pt-2 tw-text-gray-500 tw-overflow-hidden">
                                <div class="tw-float-right" v-if="module._isInstalled">
                                    <a v-if="module._isInstalled && module._hasConfig" href="javascript:;"
                                       :data-tk-event="'ModuleStore,Config,'+module.name"
                                       @click="doConfig(module)">
                                        <i class="iconfont icon-cog"></i> 配置
                                    </a>
                                </div>
                                <div v-if="module._isSystem" class="ub-text-muted tw-inline-block"><i
                                    class="iconfont icon-tag"></i><span
                                    v-html="$highlight(module.name,search.keywords)"></span></div>
                                <a v-if="module._isInstalled && !module._isLocal && module.latestVersion && versionCompare(module.latestVersion,module._localVersion)>0"
                                   :data-tk-event="'ModuleStore,UpgradeSystem,'+module.name"
                                   @click="doUpgrade(module)"
                                   href="javascript:;" class="ub-text-warning tw-mr-4">
                                    <i class="iconfont icon-direction-up"></i> 升级
                                </a>
                            </div>
                            <div v-if="!module._isSystem"
                                 class="tw-border-0 tw-border-solid tw-border-t tw-border-gray-100 tw-mt-2 tw-pt-2 tw-text-gray-500">
                                <div class="ub-text-muted tw-inline-block"><i class="iconfont icon-tag"></i><span
                                    v-html="$highlight(module.name,search.keywords)"></span></div>
                                <span class="ub-text-muted">|</span>
                                <span class="ub-text-muted" v-if="module._isLocal">
                                    版本V{{ module._localVersion }}
                                </span>
                                <span class="ub-text-muted" v-else-if="module._isInstalled">
                                    已安装V{{ module._localVersion }},最新版V{{ module.latestVersion }}
                                </span>
                                <span class="ub-text-muted" v-else>
                                    最新版V{{ module.latestVersion }}
                                </span>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
        <el-dialog :visible.sync="memberUserShow" append-to-body custom-class="pb-member-info-dialog">
            <div slot="title">
                <a href="https://modstart.com" target="_blank">
                    <img class="tw-h-8" :src="$url.cdn('vendor/ModuleStore/image/logo_modstart.png')"/>
                </a>
            </div>
            <div v-if="!memberUser.id">
                <div style="padding:1.875rem;">
                    <div class="margin-bottom tw-font-bold">
                        <i class="iconfont icon-code-alt"></i>
                        任意搭配市场的模块,让系统百变
                    </div>
                    <div class="ub-form vertical">
                        <div class="line margin-bottom">
                            <div class="field">
                                <input type="text" class="form" v-model="memberLoginInfo.username"
                                       @keyup="doSubmitCheck" placeholder="输入用户名"/>
                            </div>
                        </div>
                        <div class="line margin-bottom">
                            <div class="field">
                                <input type="password" class="form" v-model="memberLoginInfo.password"
                                       @keyup="doSubmitCheck"
                                       placeholder="输入密码"/>
                            </div>
                        </div>
                        <div class="line margin-bottom">
                            <div class="field">
                                <div class="row no-gutters">
                                    <div class="col-8">
                                        <input type="text" class="form" v-model="memberLoginInfo.captcha"
                                               autocomplete="off" @keyup="doSubmitCheck"
                                               placeholder="图片验证码"/>
                                    </div>
                                    <div class="col-4">
                                        <img class="captcha" title="刷新验证" :src="memberLoginCaptchaImage"
                                             @click="doMemberLoginCaptchaRefresh()"/>
                                    </div>
                                </div>
                            </div>
                        </div>
                        <div class="line">
                            <div class="field">
                                <el-checkbox v-model="memberLoginInfo.agree">
                                    同意
                                    <a target="_blank"
                                       href="https://modstart.com/article/module_agreement">《使用协议》</a>
                                    <a target="_blank" href="https://modstart.com/article/disclaimer">《免责声明》</a>
                                </el-checkbox>
                            </div>
                        </div>
                        <div class="line">
                            <div class="field">
                                <button type="submit" class="btn btn-primary btn-block btn-round"
                                        @click="doMemberLoginSubmit()">
                                    登录
                                </button>
                            </div>
                        </div>
                        <div class="line">
                            <div class="field">
                                <div class="tw-float-right">
                                    <a href="javascript:;" style="color:#19B84D;" @click="doScanLogin()">
                                        <i class="iconfont icon-wechat"></i>
                                        微信扫一扫
                                    </a>
                                </div>
                                还没有账号?<a href="https://modstart.com" target="_blank">立即注册</a>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
            <div v-else>
                <div>
                    <div
                        class="tw-bg-white tw-rounded-sm tw-mb-2 tw-box tw-px-5 tw-py-3 tw-mb-3 tw-flex tw-items-center tw-zoom-in">
                        <div class="tw-w-10 tw-h-10 tw-flex-none tw-image-fit tw-rounded-full tw-overflow-hidden">
                            <div class="circle tw-border tw-border-gray-200 tw-border-solid tw-shadow ub-cover-1-1"
                                 :style="{backgroundImage:`url(${memberUser.avatar||'/asset/image/avatar.svg'})`}"></div>
                        </div>
                        <div class="tw-ml-4 tw-mr-auto">
                            <div class="tw-font-medium">{{ memberUser.username || '' }}</div>
                        </div>
                        <div>
                            <a href="javascript:;" @click="doMemberUserLogout()">退出</a>
                        </div>
                    </div>
                    <div class="tw-p-4">
                        <a class="btn btn-round" href="https://modstart.com/member_ms_module_license" target="_blank">
                            <i class="iconfont icon-lock"></i>
                            已授权的模块
                        </a>
                        <a class="btn btn-round" href="https://modstart.com/developer" target="_blank">
                            <i class="iconfont icon-code"></i>
                            成为模块开发者
                        </a>
                    </div>
                </div>
            </div>
        </el-dialog>
        <el-dialog :visible.sync="commandDialogShow"
                   :show-close="commandDialogFinish"
                   :close-on-press-escape="false"
                   :close-on-click-modal="false"
                   append-to-body>
            <div slot="title">
                <div class="ub-text-bold ub-text-primary">
                    <i class="iconfont icon-code"></i>
                    {{ commandDialogTitle }}
                </div>
            </div>
            <div class="tw-bg-gray-900 tw-font-mono tw-leading-8 tw-p-4 tw-text-white" v-if="commandDialogShow">
                <div v-for="(msg,msgIndex) in commandDialogMsgs" v-html="msg"></div>
                <div v-if="!commandDialogFinish">
                    <i class="iconfont icon-loading tw-inline-block tw-animate-spin"></i>
                    当前操作已运行 {{ commandDialogRunElapse }} s ...
                </div>
                <div v-else>
                    <i class="iconfont icon-check"></i>
                    操作已运行完成
                </div>
            </div>
            <div class="tw-p-4 tw-text-center" v-if="commandDialogFinish">
                <el-button type="danger" @click="commandDialogShow=false">关闭</el-button>
            </div>
        </el-dialog>
        <el-dialog :visible.sync="installVersionDialogShow"
                   :close-on-press-escape="false"
                   :close-on-click-modal="false"
                   append-to-body>
            <div slot="title">
                <div class="ub-text-bold ub-text-primary" v-if="installVersionModule">
                    <i class="iconfont icon-code"></i>
                    安装 {{ installVersionModule.title }} 其他版本
                </div>
            </div>
            <div v-if="installVersionModule">
                <table class="ub-table tw-w-full tw-font-mono">
                    <tbody>
                    <tr v-for="(v,vIndex) in installVersionReleases">
                        <td width="100">v{{ v.version }}</td>
                        <td>
                            <span class="ub-tag warning" v-if="v.status==='preview'">预览</span>
                            {{ v.feature }}
                        </td>
                        <td width="100">{{ v.time }}</td>
                        <td>
                            <a href="javascript:;" @click="doInstallVersionSubmit(installVersionModule,v.version)">
                                <i class="iconfont icon-plus"></i>
                                安装
                            </a>
                        </td>
                    </tr>
                    </tbody>
                </table>
            </div>
        </el-dialog>
    </div>
</template>

<script>
import {BeanUtil} from '@ModStartAsset/svue/lib/util'
import {Storage} from '@ModStartAsset/svue/lib/storage'

const UrlWatcher = require('@ModStartAsset/lib/urlWatcher.js');


export default {
    name: "ModuleStore",
    data() {
        return {
            loading: true,
            search: {
                tab: MS.url.getQuery('tab', 'store'),
                priceType: 'all',
                isRecommend: false,
                categoryId: 0,
                type: '',
                keywords: '',
            },
            commandDialogMsgs: [],
            commandDialogRunStart: 0,
            commandDialogRunElapse: 0,
            commandDialogShow: false,
            commandDialogFinish: false,
            commandDialogTitle: '',
            memberUserShow: false,
            memberUserLoading: false,
            memberLoginCaptchaImage: null,
            memberLoginInfo: {
                username: '',
                password: '',
                captcha: '',
                agree: false,
            },
            storeApiToken: Storage.get('storeApiToken', ''),
            memberUser: {
                id: 0,
                username: '',
                avatar: '',
            },
            categories: [],
            types: [],
            modules: [],
            storeConfig: {
                disable: false,
            },
            installVersionDialogShow: false,
            installVersionReleases: [],
            installVersionModule: null,
            payWatcher: null,
        }
    },
    watch: {
        memberUser: {
            handler(n, o) {
                this.doLoad()
            },
            deep: true
        }
    },
    computed: {
        filterModules() {
            const results = this.modules.filter(module => {
                switch (this.search.tab) {
                    case 'store':
                        if (module._isLocal) return false
                        if (module._isSystem) return false
                        break
                    case 'installed':
                        if (!module._isInstalled) return false
                        if (module._isSystem) return false
                        break;
                    case 'enabled':
                        if (!module._isEnabled) return false
                        if (module._isSystem) return false
                        break;
                    case 'disabled':
                        if (!module._isInstalled || module._isEnabled) return false
                        if (module._isSystem) return false
                        break;
                    case 'local':
                        if (!module._isLocal) return false
                        if (module._isSystem) return false
                        break
                    case 'system':
                        if (!module._isSystem) return false
                        break
                    case 'upgradeable':
                        if (!(module._isInstalled && !module._isLocal && this.canUpgrade(module._localVersion, module.latestVersion))) {
                            return false
                        }
                        if (module._isSystem) return false
                        break
                }
                if (this.search.isRecommend) {
                    if (!module.isRecommend) {
                        return false
                    }
                }
                if (!!this.search.type) {
                    if (!module.types.includes(this.search.type)) {
                        return false
                    }
                }
                switch (this.search.priceType) {
                    case 'free':
                        if (module.isFee) return false
                        break
                    case 'fee':
                        if (!module.isFee) return false
                        break
                }
                if (this.search.categoryId) {
                    if (module.categoryId !== this.search.categoryId) return false
                }
                if (this.search.keywords) {
                    if (module.title.toLowerCase().includes(this.search.keywords.toLowerCase())) {
                        return true
                    }
                    if (module.name.toLowerCase().includes(this.search.keywords.toLowerCase())) {
                        return true
                    }
                    if (module.description.toLowerCase().includes(this.search.keywords.toLowerCase())) {
                        return true
                    }
                    return false
                }
                return true
            })
            return results
        }
    },
    mounted() {
        this.doLoad()
        this.doLoadStore()
        setInterval(() => {
            this.commandDialogRunElapse = parseInt(((new Date()).getTime() - this.commandDialogRunStart) / 1000)
        }, 1000)
    },
    methods: {
        versionCompare(left, right) {
            let a = left.split('.'), b = right.split('.')
            for (let i = 0, len = Math.max(a.length, b.length); i < len; i++) {
                if ((a[i] && !b[i] && parseInt(a[i]) > 0) || (parseInt(a[i]) > parseInt(b[i]))) {
                    return 1;
                } else if ((b[i] && !a[i] && parseInt(b[i]) > 0) || (parseInt(a[i]) < parseInt(b[i]))) {
                    return -1;
                }
            }
            return 0;
        },
        canUpgrade(currentVersion, latestVersion) {
            return this.versionCompare(currentVersion, latestVersion) < 0
        },
        doStoreRequest(url, data, cbSuccess, cbError) {
            const cbErrorDefault = (res) => {
                this.$dialog.tipError(res.msg)
            }
            if (!cbError) {
                cbError = cbErrorDefault
            }
            $.ajax({
                url: `${window.__data.apiBase}/api/${url}`,
                dataType: 'jsonp',
                timeout: 10 * 1000,
                data: Object.assign(data, {
                    api_token: this.storeApiToken,
                    modstartParam: JSON.stringify(window.__data.modstartParam),
                }),
                success: (res) => {
                    if (res.code) {
                        if (res.code === 1002) {
                            this.doMemberLoginCaptchaRefresh()
                        }
                        if (true !== cbError(res)) {
                            cbErrorDefault(res)
                        }
                    } else {
                        cbSuccess(res)
                    }
                },
                error: (res) => {
                    if (true !== cbError({code: -1, msg: '请求出现错误'})) {
                        cbErrorDefault({code: -1, msg: '请求出现错误'})
                    }
                },
                jsonp: 'callback',
            });
        },
        doMemberUserLogout() {
            this.$dialog.confirm('确认退出?', () => {
                this.storeApiToken = ''
                Storage.set('storeApiToken', '')
                this.memberUserShow = false
                this.doLoadStore()
            })
        },
        doSubmitCheck(e) {
            if (e.keyCode === 13) {
                this.doMemberLoginSubmit()
            }
        },
        doMemberLoginSubmit() {
            if (!this.memberLoginInfo.username || !this.memberLoginInfo.password || !this.memberLoginInfo.captcha) {
                return
            }
            if (!this.memberLoginInfo.agree) {
                this.$dialog.tipError('请先同意使用协议')
                return
            }
            const postData = {
                username: this.memberLoginInfo.username,
                password: this.memberLoginInfo.password,
                captcha: this.memberLoginInfo.captcha,
            }
            postData.ek = MS.util.randomString(8)
            postData.username = 'E.A:' + MS.util.encrypt.aesEncode(postData.ek, postData.username)
            postData.password = 'E.A:' + MS.util.encrypt.aesEncode(postData.ek, postData.password)
            this.$dialog.loadingOn()
            this.doStoreRequest('store/login', postData, res => {
                this.$dialog.loadingOff()
                this.$dialog.tipSuccess('登录成功')
                this.doLoadStoreMember()
                this.memberLoginInfo.username = ''
                this.memberLoginInfo.password = ''
                this.memberLoginInfo.captcha = ''
                this.memberUserShow = false
            }, res => {
                this.$dialog.loadingOff()
            })
        },
        doMemberLoginCaptchaRefresh(cb) {
            this.doStoreRequest('store/login_captcha', {}, res => {
                this.memberLoginCaptchaImage = res.data.image
                cb && cb()
            })
        },
        doMemberLoginShow() {
            if (this.memberUser.id > 0) {
                this.memberUserShow = true
            } else {
                this.$dialog.loadingOn()
                this.doMemberLoginCaptchaRefresh(() => {
                    this.$dialog.loadingOff()
                    this.memberUserShow = true
                })
            }
        },
        doLoadStoreMember() {
            this.memberUserLoading = true
            this.doStoreRequest('store/member', {}, res => {
                this.memberUserLoading = false
                BeanUtil.update(this.memberUser, res.data)
            }, res => {
                this.memberUserLoading = false
            })
        },
        doLoadStore() {
            if (!!this.storeApiToken) {
                this.doLoadStoreMember()
            } else {
                this.doStoreRequest('store/config', {}, res => {
                    this.storeApiToken = res.data.apiToken
                    Storage.set('storeApiToken', res.data.apiToken)
                    this.doLoadStoreMember()
                })
            }
        },
        doLoad() {
            this.$api.post(this.$url.admin('module_store/all'), {
                memberUserId: this.memberUser.id,
                apiToken: this.storeApiToken,
            }, res => {
                this.loading = false
                this.categories = res.data.categories
                this.types = res.data.types
                this.modules = res.data.modules
                this.storeConfig = res.data.storeConfig
            })
        },
        commandDialogMsgsPush(msg) {
            if (!msg) {
                return
            }
            if (!Array.isArray(msg)) {
                msg = [msg]
            }
            msg = msg.map(m => {
                m = m.trim()
                if (!m.startsWith('<')) {
                    m = '<i class="iconfont icon-hr"></i> ' + m
                }
                return m
            })
            this.commandDialogMsgs = this.commandDialogMsgs.concat(msg)
        },
        doCommand(command, data, step, title) {
            step = step || null
            title = title || null
            if (null === step) {
                this.commandDialogMsgs = []
                this.commandDialogShow = true
                this.commandDialogFinish = false
            }
            if (title) {
                this.commandDialogTitle = title
                this.commandDialogMsgsPush(title)
            }
            this.commandDialogRunStart = (new Date()).getTime()
            this.commandDialogRunElapse = 0
            this.$api.post(this.$url.admin(`module_store/${command}`), {
                token: this.storeApiToken,
                step: step,
                data: JSON.stringify(data)
            }, res => {
                this.commandDialogMsgsPush(res.data.msg)
                if (res.data.finish) {
                    this.commandDialogFinish = true
                    this.doLoad()
                    return
                } else {
                    setTimeout(() => {
                        this.doCommand(res.data.command, res.data.data, res.data.step)
                    }, 1000)
                }
            }, res => {
                this.commandDialogMsgsPush('<i class="iconfont icon-close ub-text-danger"></i> <span class="ub-text-danger">' + res.msg + '</span>')
                if (res.data && res.data.msg) {
                    this.commandDialogMsgsPush(res.data.msg)
                }
                if (res.data && res.data.payWatchUrl) {
                    this.startPayWatch(res.data.buyCodeId, res.data.payWatchUrl)
                }
                this.commandDialogFinish = true
                return true
            })
        },
        startPayWatch(buyCodeId, payWatchUrl) {
            const buyCodeVisible = () => {
                return ($('[data-buy-code=' + buyCodeId + ']').length > 0)
            }
            this.$nextTick(() => {
                if (this.urlWatcher) {
                    this.urlWatcher.stop()
                }
                if (!buyCodeVisible()) {
                    return;
                }
                this.urlWatcher = new UrlWatcher({
                    url: payWatchUrl,
                    jsonp: true,
                    data: {},
                    maxRound: 100,
                    requestFinish: (res) => {
                        if (!buyCodeVisible()) {
                            this.urlWatcher.stop()
                            return;
                        }
                        MS.api.defaultCallback(res, {
                            success: (res) => {
                                switch (res.data.status) {
                                    case 'Payed':
                                        this.$dialog.alertSuccess('支付成功,请关闭弹窗重新安装', () => {
                                            this.commandDialogShow = false
                                        })
                                        break;
                                    case 'WaitPay':
                                        this.urlWatcher.next();
                                        break;
                                }
                            }
                        });
                    },
                    expired: () => {
                        this.$dialog.alertError('支付超时,请关闭弹窗重新请求支付二维码', () => {
                            this.commandDialogShow = false
                        })
                    },
                })
                this.urlWatcher.start()
            })
        },
        doInstallVersion(module) {
            this.$dialog.loadingOn()
            this.doStoreRequest('store/module_releases', {module: module.name}, res => {
                this.$dialog.loadingOff()
                this.installVersionModule = module
                this.installVersionReleases = res.data.releases
                this.installVersionDialogShow = true
            }, res => {
                this.$dialog.loadingOff()
            })
        },
        doInstall(module) {
            if (!this.memberUser.id) {
                this.doMemberLoginShow()
                return
            }
            this.doCommand('install', {
                module: module.name,
                version: module.latestVersion,
                isLocal: module._isLocal
            }, null, `安装模块 ${module.title}(${module.name}) V${module.latestVersion}`)
        },
        doInstallVersionSubmit(module, version) {
            if (!this.memberUser.id) {
                this.doMemberLoginShow()
                return
            }
            this.doCommand('install', {
                module: module.name,
                version: version,
                isLocal: module._isLocal
            }, null, `安装模块 ${module.title}(${module.name}) V${version}`)
        },
        doDisable(module) {
            this.doCommand('disable', {
                module: module.name,
                version: module._localVersion
            }, null, `禁用模块 ${module.title}(${module.name})`)
        },
        doEnable(module) {
            this.doCommand('enable', {
                module: module.name,
                version: module._localVersion
            }, null, `启用模块 ${module.title}(${module.name})`)
        },
        doUninstall(module) {
            this.$dialog.confirm('确认卸载?', () => {
                this.doCommand('uninstall', {
                    module: module.name,
                    version: module._localVersion,
                    isLocal: module._isLocal
                }, null, `卸载模块 ${module.title}(${module.name})`)
            })
        },
        doUpgrade(module) {
            if (!this.memberUser.id) {
                this.doMemberLoginShow()
                return
            }
            this.$dialog.confirm('确认升级?', () => {
                this.doCommand('upgrade', {
                    module: module.name,
                    version: module.latestVersion,
                }, null, `升级模块 ${module.title}(${module.name}) V${module.latestVersion}`)
            })
        },
        doConfig(module) {
            this.$dialog.dialog(this.$url.admin(`module_store/config/${module.name}`))
        },
        doScanLogin() {
            if (!this.memberLoginInfo.agree) {
                this.$dialog.tipError('请先同意相关协议')
                return
            }
            this.$dialog.loadingOn()
            this.doStoreRequest('store/login_wechatmp_qrcode', {}, res => {
                this.$dialog.loadingOff()
                let isOpen = false
                let dialog = null
                const checkLogin = () => {
                    if (!isOpen) {
                        return
                    }
                    this.doStoreRequest('store/login_wechatmp_info', {}, res => {
                        if (res.data.memberUserId) {
                            this.doLoadStoreMember()
                            this.$dialog.dialogClose(dialog)
                            this.$dialog.tipSuccess('登录成功')
                        } else if (res.data.oauthUserInfo) {
                            this.$dialog.dialogClose(dialog)
                            this.$dialog.alertError('当前微信未绑定账号,请先注册')
                        } else {
                            setTimeout(() => {
                                checkLogin()
                            }, 3000)
                        }
                    })
                }
                checkLogin();
                dialog = this.$dialog.dialogContent(`<img style="width:200px;height:200px;" src="${res.data.qrcode}" />`, {
                    openCallback: () => {
                        isOpen = true
                        setTimeout(() => {
                            checkLogin()
                        }, 3000)
                    },
                    closeCallback: () => {
                        isOpen = false
                    },
                })
            }, res => {
                this.$dialog.loadingOff()
            })
        }
    }
}
</script>

<style lang="less">
.pb-member-info-dialog {
    max-width: 22.5rem;
}
</style>