| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981 |
- <template>
- <div class="app-container">
- <!-- ===== 搜索栏 ===== -->
- <el-card shadow="never" class="mb16 filter-card">
- <el-form :model="queryParams" ref="queryForm" :inline="true" size="small">
- <el-form-item label="租户名称" prop="tenantName">
- <el-input v-model="queryParams.tenantName" placeholder="租户编码/名称" clearable @keyup.enter.native="handleQuery" />
- </el-form-item>
- <el-form-item label="状态" prop="status">
- <el-select v-model="queryParams.status" placeholder="全部" clearable style="width:120px">
- <el-option v-for="item in statusOptions" :key="item.value" :label="item.label" :value="item.value" />
- </el-select>
- </el-form-item>
- <el-form-item>
- <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">查询</el-button>
- <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
- </el-form-item>
- </el-form>
- </el-card>
- <!-- ===== 操作栏 ===== -->
- <el-row :gutter="10" class="mb8">
- <el-col :span="1.5">
- <el-button type="primary" icon="el-icon-plus" size="mini" @click="handleAdd" v-hasPermi="['admin:platform:edit']">新增租户</el-button>
- </el-col>
- <el-col :span="1.5">
- <el-button type="warning" plain icon="el-icon-download" size="mini" :loading="exportLoading" @click="handleExport">导出</el-button>
- </el-col>
- <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
- </el-row>
- <!-- ===== 租户列表 ===== -->
- <el-table border v-loading="loading" :data="companyList" size="small" style="width:100%">
- <el-table-column label="租户编码" prop="tenantCode" min-width="80" align="center" />
- <el-table-column label="租户名称" prop="tenantName" min-width="120" />
- <el-table-column label="联系人" prop="contactName" min-width="80" align="center" />
- <el-table-column label="联系电话" prop="contactPhone" min-width="110" align="center" />
- <el-table-column label="余额" align="center" min-width="100">
- <template slot-scope="scope">
- <span style="color:#1890ff;font-weight:bold">{{ scope.row.balance ? '¥' + Number(scope.row.balance).toFixed(2) : '¥0.00' }}</span>
- </template>
- </el-table-column>
- <el-table-column label="已消费总额" align="center" min-width="100">
- <template slot-scope="scope">
- <span>{{ scope.row.totalConsumption ? '¥' + Number(scope.row.totalConsumption).toFixed(2) : '¥0.00' }}</span>
- </template>
- </el-table-column>
- <el-table-column label="开通账户数" align="center" prop="accountCount" min-width="90" />
- <el-table-column label="绑定企微账户数" align="center" prop="qwAccountCount" min-width="110" />
- <el-table-column label="绑定个微账户数" align="center" prop="wxAccountCount" min-width="110" />
- <el-table-column label="客户数" align="center" prop="customerCount" min-width="80" />
- <el-table-column label="企微用户数" align="center" prop="qwUserCount" min-width="90" />
- <el-table-column label="过期时间" align="center" prop="expireTime" min-width="100" />
- <el-table-column label="归属代理" align="center" prop="proxyName" min-width="90" />
- <el-table-column label="状态" align="center" min-width="70">
- <template slot-scope="scope">
- <el-tag v-if="scope.row.status == 1" type="success" size="mini">正常</el-tag>
- <el-tag v-else type="danger" size="mini">禁用</el-tag>
- </template>
- </el-table-column>
- <el-table-column label="操作" align="center" width="380" fixed="right" class-name="small-padding fixed-width">
- <template slot-scope="scope">
- <el-button size="mini" type="text" icon="el-icon-edit" @click="handleView(scope.row)">编辑</el-button>
- <el-button size="mini" type="text" icon="el-icon-key" style="color:#722ed1" @click="handleResetPwd(scope.row)">重置密码</el-button>
- <el-button size="mini" type="text" style="color:#52c41a" icon="el-icon-coin" @click="handleRecharge(scope.row)">充值/扣款</el-button>
- <el-button size="mini" type="text" style="color:#722ed1" icon="el-icon-menu" @click="handleEditMenu(scope.row, 'sys')">管理端菜单</el-button>
- <el-button size="mini" type="text" style="color:#e6a23c" icon="el-icon-price-tag" @click="handleModulePricing(scope.row)">模块定价</el-button>
- <el-button size="mini" type="text" style="color:#13c2c2" icon="el-icon-sell" @click="handleEditMenu(scope.row, 'com')">销售菜单</el-button>
- <el-button
- v-if="scope.row.status == 1"
- size="mini" type="text" style="color:#fa8c16"
- icon="el-icon-lock"
- @click="handleDisable(scope.row)"
- v-hasPermi="['admin:platform:edit']"
- >禁用</el-button>
- <el-button
- v-else
- size="mini" type="text" style="color:#52c41a"
- icon="el-icon-unlock"
- @click="handleEnable(scope.row)"
- v-hasPermi="['admin:platform:edit']"
- >启用</el-button>
- </template>
- </el-table-column>
- </el-table>
- <pagination v-show="total>0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" @pagination="getList" />
- <!-- ===== 编辑租户弹窗 ===== -->
- <el-dialog title="编辑租户" :visible.sync="viewOpen" width="720px" append-to-body destroy-on-close class="tenant-dialog">
- <el-form ref="editForm" :model="viewForm" :rules="editRules" label-width="112px" size="small" class="tenant-dialog-form">
- <el-row :gutter="16">
- <el-col :span="12">
- <el-form-item label="租户ID">
- <span>{{ viewForm.id }}</span>
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="租户编码">
- <span>{{ viewForm.tenantCode || '-' }}</span>
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="租户名称" prop="tenantName">
- <el-input v-model="viewForm.tenantName" placeholder="请输入租户名称" />
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="联系电话" prop="contactPhone">
- <el-input v-model="viewForm.contactPhone" placeholder="请输入联系电话" />
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="联系人" prop="contactName">
- <el-input v-model="viewForm.contactName" placeholder="请输入联系人" />
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="过期时间" prop="expireTime">
- <el-date-picker v-model="viewForm.expireTime" type="date" value-format="yyyy-MM-dd" placeholder="选择过期时间" style="width:100%" />
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="归属代理" prop="proxyId">
- <el-select v-model="viewForm.proxyId" placeholder="请选择代理" clearable filterable style="width:100%">
- <el-option v-for="p in proxyOptions" :key="p.proxyId" :label="p.proxyName" :value="p.proxyId" />
- </el-select>
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="租户状态">
- <el-switch v-model="viewForm.statusBool" active-text="正常" inactive-text="禁用"
- :active-value="1" :inactive-value="0" />
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="公司数量" prop="companyNum">
- <el-input-number v-model="viewForm.companyNum" :min="1" :precision="0" controls-position="right" style="width:100%" />
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item prop="accountNum">
- <span slot="label" class="tenant-form-label" title="公司员工账户总数量">员工账户数</span>
- <el-input-number v-model="viewForm.accountNum" :min="1" :precision="0" controls-position="right" style="width:100%" />
- </el-form-item>
- </el-col>
- <el-col :span="24">
- <el-form-item label="租户余额" class="tenant-balance-item">
- <span style="color:#1890ff;font-weight:bold">¥{{ viewForm.balance || '0.00' }}</span>
- <span class="tenant-balance-hint">(通过充值/扣款调整)</span>
- </el-form-item>
- </el-col>
- </el-row>
- </el-form>
- <div slot="footer">
- <el-button @click="viewOpen = false">取 消</el-button>
- <el-button type="primary" :loading="editSubmitting" @click="submitEdit">保 存</el-button>
- </div>
- </el-dialog>
- <!-- ===== 新增租户弹窗 ===== -->
- <el-dialog title="新增租户" :visible.sync="addDialog.visible" width="720px" append-to-body destroy-on-close class="tenant-dialog">
- <el-form ref="addForm" :model="addDialog.form" :rules="addDialog.rules" label-width="112px" size="small" class="tenant-dialog-form">
- <el-row :gutter="16">
- <el-col :span="12">
- <el-form-item label="企业名称" prop="tenantName">
- <el-input v-model="addDialog.form.tenantName" placeholder="请输入企业名称" />
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="联系电话" prop="contactPhone">
- <el-input v-model="addDialog.form.contactPhone" placeholder="请输入联系电话" />
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="联系人" prop="contactName">
- <el-input v-model="addDialog.form.contactName" placeholder="请输入联系人" />
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="商务负责人" prop="manager">
- <el-input v-model="addDialog.form.manager" placeholder="请输入商务负责人" />
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="管理员账号" prop="userName">
- <el-input v-model="addDialog.form.userName" placeholder="请输入管理员账号" />
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="密码" prop="password">
- <el-input v-model="addDialog.form.password" type="password" show-password placeholder="请输入密码">
- <el-button slot="append" icon="el-icon-refresh" @click="generateAddPwd" title="随机生成密码">生成</el-button>
- </el-input>
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="开始时间" prop="startTime">
- <el-date-picker v-model="addDialog.form.startTime" type="date" value-format="yyyy-MM-dd" placeholder="选择开始时间" style="width:100%" />
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="到期时间" prop="expireTime">
- <el-date-picker v-model="addDialog.form.expireTime" type="date" value-format="yyyy-MM-dd" placeholder="选择到期时间" style="width:100%" />
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="归属代理" prop="proxyId">
- <el-select v-model="addDialog.form.proxyId" placeholder="请选择代理" clearable filterable style="width:100%">
- <el-option v-for="p in proxyOptions" :key="p.proxyId" :label="p.proxyName" :value="p.proxyId" />
- </el-select>
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="公司数量" prop="companyNum">
- <el-input-number v-model="addDialog.form.companyNum" :min="1" :precision="0" controls-position="right" style="width:100%" />
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item prop="accountNum">
- <span slot="label" class="tenant-form-label" title="公司员工账户总数量">员工账户数</span>
- <el-input-number v-model="addDialog.form.accountNum" :min="1" :precision="0" controls-position="right" style="width:100%" />
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="状态" prop="status" class="tenant-status-item">
- <el-radio-group v-model="addDialog.form.status">
- <el-radio :label="1">启用</el-radio>
- <el-radio :label="0">禁用</el-radio>
- </el-radio-group>
- </el-form-item>
- </el-col>
- </el-row>
- </el-form>
- <div slot="footer">
- <el-button @click="addDialog.visible = false">取 消</el-button>
- <el-button type="primary" :loading="addDialog.submitting" @click="submitAdd">确 定</el-button>
- </div>
- </el-dialog>
- <!-- ===== 充值/扣款弹窗 ===== -->
- <el-dialog :title="rechargeTitle" :visible.sync="rechargeOpen" width="420px" append-to-body>
- <el-form ref="rechargeForm" :model="rechargeForm" :rules="rechargeRules" label-width="90px">
- <el-form-item label="租户名称">
- <span>{{ rechargeForm.companyName }}</span>
- </el-form-item>
- <el-form-item label="当前余额">
- <span style="color:#1890ff;font-weight:bold">¥{{ rechargeForm.currentBalance || '0.00' }}</span>
- </el-form-item>
- <el-form-item label="操作类型" prop="operateType">
- <el-radio-group v-model="rechargeForm.operateType">
- <el-radio label="recharge">充值</el-radio>
- <el-radio label="deduct">扣款</el-radio>
- </el-radio-group>
- </el-form-item>
- <el-form-item label="金额" prop="amount">
- <el-input-number v-model="rechargeForm.amount" :min="0.01" :precision="2" style="width:200px" />
- </el-form-item>
- <el-form-item label="备注" prop="remark">
- <el-input v-model="rechargeForm.remark" type="textarea" :rows="2" placeholder="请输入备注" />
- </el-form-item>
- </el-form>
- <div slot="footer">
- <el-button @click="rechargeOpen = false">取 消</el-button>
- <el-button type="primary" @click="submitRecharge">确 定</el-button>
- </div>
- </el-dialog>
- <!-- ===== 菜单编辑弹窗(租户列表:管理端菜单 / 销售菜单) ===== -->
- <el-dialog :title="menuDialog.title" :visible.sync="menuDialog.visible" width="560px" append-to-body destroy-on-close @opened="onMenuDialogOpened">
- <div v-loading="menuDialog.loading" class="menu-tree-scroll">
- <div v-if="menuDialog.treeReady && menuDialog.treeData.length" class="menu-tree-toolbar">
- <el-button type="text" size="mini" @click="checkAllMenuNodes">全选</el-button>
- <el-button type="text" size="mini" @click="uncheckAllMenuNodes">全不选</el-button>
- <el-button type="text" size="mini" @click="expandAllMenuNodes">展开全部</el-button>
- <el-button type="text" size="mini" @click="collapseAllMenuNodes">收起全部</el-button>
- </div>
- <el-tree
- v-if="menuDialog.treeReady"
- ref="menuTree"
- :data="menuDialog.treeData"
- show-checkbox
- node-key="menuId"
- :props="menuTreeProps"
- :expand-on-click-node="false"
- >
- <span slot-scope="{ data }" class="custom-tree-node">
- <span>{{ data.menuName }}</span>
- <el-tag v-if="data.menuType === 'M'" size="mini" style="margin-left:6px">目录</el-tag>
- <el-tag v-else-if="data.menuType === 'C'" type="success" size="mini" style="margin-left:6px">菜单</el-tag>
- <el-tag v-else-if="data.menuType === 'F'" type="warning" size="mini" style="margin-left:6px">按钮</el-tag>
- <el-tag v-if="data.visible === '0'" type="success" size="mini" style="margin-left:6px">已分配</el-tag>
- <el-tag v-else type="info" size="mini" style="margin-left:6px">未分配</el-tag>
- </span>
- </el-tree>
- <el-empty v-if="!menuDialog.loading && menuDialog.treeReady && !menuDialog.treeData.length" description="暂无菜单数据" />
- </div>
- <div slot="footer">
- <el-button @click="menuDialog.visible = false">取 消</el-button>
- <el-button type="primary" :loading="menuDialog.submitting" @click="submitMenuEdit">确 定</el-button>
- </div>
- </el-dialog>
- <!-- ===== 模块定价弹窗 ===== -->
- <el-dialog :title="'模块定价 - ' + pricingDialog.tenantName" :visible.sync="pricingDialog.visible" width="800px" append-to-body destroy-on-close>
- <div v-loading="pricingDialog.loading">
- <el-table :data="pricingDialog.modules" border size="small" style="width:100%">
- <el-table-column label="模块" align="center" prop="moduleName" min-width="120" />
- <el-table-column label="全局售价" align="center" min-width="100">
- <template slot-scope="scope">
- <span style="color:#909399">{{ scope.row.globalPrice != null ? scope.row.globalPrice : '-' }}</span>
- </template>
- </el-table-column>
- <el-table-column label="全局成本" align="center" min-width="100">
- <template slot-scope="scope">
- <span style="color:#909399">{{ scope.row.globalCost != null ? scope.row.globalCost : '-' }}</span>
- </template>
- </el-table-column>
- <el-table-column label="租户售价" align="center" min-width="130">
- <template slot-scope="scope">
- <el-input-number v-model="scope.row.price" :precision="4" :min="0" :step="0.01" size="small" style="width:150px" placeholder="跟随全局" />
- </template>
- </el-table-column>
- <el-table-column label="成本价" align="center" min-width="130">
- <template slot-scope="scope">
- <el-input-number v-model="scope.row.costPrice" :precision="4" :min="0" :step="0.01" size="small" style="width:150px" placeholder="跟随全局" />
- </template>
- </el-table-column>
- <el-table-column label="状态" align="center" min-width="80">
- <template slot-scope="scope">
- <el-switch v-model="scope.row.status" :active-value="1" :inactive-value="0" active-text="启用" inactive-text="禁" size="small" />
- </template>
- </el-table-column>
- </el-table>
- <div style="color:#909399;font-size:12px;margin-top:8px">* 租户售价/成本价留空或填0则使用全局定价</div>
- </div>
- <div slot="footer">
- <el-button @click="pricingDialog.visible = false">取 消</el-button>
- <el-button type="primary" :loading="pricingDialog.submitting" @click="submitModulePricing">保 存</el-button>
- </div>
- </el-dialog>
- </div>
- </template>
- <script>
- import { listAllCompanies, getCompanyInfo, addCompany, updateTenant, disableCompany, enableCompany, rechargeCompany, exportCompany, getTenantMenuTree, editTenantMenu, resetTenantPwd } from '@/api/admin/sysCompany'
- import { listTrafficPricing, addTrafficPricing, updateTrafficPricing } from '@/api/admin/trafficPricing'
- import { allEnabledProxies } from '@/api/admin/proxy'
- import { listServiceCost } from '@/api/admin/serviceCost'
- import { generateRandomPwd } from '@/utils/index'
- export default {
- name: 'SysCompanyAdmin',
- data() {
- return {
- loading: true,
- exportLoading: false,
- showSearch: true,
- total: 0,
- companyList: [],
- statusOptions: [
- { value: 1, label: '正常' },
- { value: 0, label: '禁用' }
- ],
- queryParams: {
- pageNum: 1,
- pageSize: 10,
- tenantName: null,
- status: null
- },
- // 详情弹窗
- viewOpen: false,
- viewForm: {},
- editSubmitting: false,
- editRules: {
- tenantName: [{ required: true, message: '请输入租户名称', trigger: 'blur' }],
- contactPhone: [{ required: true, message: '请输入联系电话', trigger: 'blur' }],
- companyNum: [{ required: true, message: '请输入公司数量', trigger: 'blur' }],
- accountNum: [{ required: true, message: '请输入员工账户数', trigger: 'blur' }]
- },
- // 新增租户弹窗
- addDialog: {
- visible: false,
- submitting: false,
- form: {
- tenantName: '',
- contactPhone: '',
- contactName: '',
- userName: '',
- password: '',
- manager: '',
- startTime: null,
- expireTime: null,
- status: 1,
- companyNum: 1,
- accountNum: 1
- },
- rules: {
- tenantName: [{ required: true, message: '请输入企业名称', trigger: 'blur' }],
- contactPhone: [{ required: true, message: '请输入联系电话', trigger: 'blur' }],
- userName: [{ required: true, message: '请输入管理员账号', trigger: 'blur' }],
- password: [{ required: true, message: '请输入密码', trigger: 'blur' }],
- companyNum: [{ required: true, message: '请输入公司数量', trigger: 'blur' }],
- accountNum: [{ required: true, message: '请输入员工账户数', trigger: 'blur' }]
- }
- },
- // 充值/扣款弹窗
- rechargeOpen: false,
- rechargeTitle: '',
- rechargeForm: { companyId: null, companyName: '', currentBalance: 0, operateType: 'recharge', amount: null, remark: '' },
- rechargeRules: {
- operateType: [{ required: true, message: '请选择操作类型', trigger: 'change' }],
- amount: [{ required: true, message: '请输入金额', trigger: 'blur' }]
- },
- menuTreeProps: { label: 'menuName', children: 'children' },
- // 菜单编辑弹窗
- menuDialog: {
- visible: false,
- title: '',
- flag: 'sys',
- companyId: null,
- companyName: '',
- treeData: [],
- treeReady: false,
- checkedKeys: [],
- checkedKeySet: null,
- initialAssignedIds: null,
- loading: false,
- submitting: false
- },
- // 模块定价弹窗
- pricingDialog: {
- visible: false,
- tenantId: null,
- tenantName: '',
- loading: false,
- submitting: false,
- modules: []
- },
- // 代理列表(下拉框用)
- proxyOptions: []
- }
- },
- created() {
- this.getList()
- this.loadProxyOptions()
- },
- methods: {
- loadProxyOptions() {
- allEnabledProxies().then(res => {
- this.proxyOptions = res.data || []
- }).catch(() => {})
- },
- getList() {
- this.loading = true
- listAllCompanies(this.queryParams).then(response => {
- this.companyList = response.rows
- this.total = response.total
- this.loading = false
- })
- },
- handleQuery() {
- this.queryParams.pageNum = 1
- this.getList()
- },
- resetQuery() {
- this.resetForm('queryForm')
- this.handleQuery()
- },
- /** 新增租户 */
- handleAdd() {
- this.addDialog.form = {
- tenantName: '',
- contactPhone: '',
- contactName: '',
- userName: '',
- password: generateRandomPwd(12),
- manager: '',
- proxyId: null,
- startTime: null,
- expireTime: null,
- status: 1,
- companyNum: 1,
- accountNum: 1
- }
- this.addDialog.visible = true
- this.$nextTick(() => {
- if (this.$refs.addForm) this.$refs.addForm.clearValidate()
- })
- },
- /** 随机生成新增密码 */
- generateAddPwd() {
- this.addDialog.form.password = generateRandomPwd(12)
- },
- /** 重置租户管理员密码 */
- handleResetPwd(row) {
- const newPwd = generateRandomPwd(12)
- this.$prompt('为租户 "' + row.tenantName + '" 重置管理员密码', '重置密码', {
- confirmButtonText: '确定',
- cancelButtonText: '取消',
- inputValue: newPwd,
- inputPattern: /^.{6,30}$/,
- inputErrorMessage: '密码长度必须介于 6 和 30 之间',
- inputType: 'text'
- }).then(({ value }) => {
- resetTenantPwd(row.id, value).then(() => {
- this.$message.success('密码重置成功,新密码为:' + value)
- }).catch(() => {
- this.$message.error('密码重置失败')
- })
- }).catch(() => {})
- },
- /** 提交新增租户 */
- submitAdd() {
- this.$refs['addForm'].validate(valid => {
- if (!valid) return
- this.addDialog.submitting = true
- addCompany(this.addDialog.form).then(() => {
- this.$message.success('新增成功')
- this.addDialog.visible = false
- this.getList()
- }).catch(() => {
- this.$message.error('新增失败,请重试')
- }).finally(() => {
- this.addDialog.submitting = false
- })
- })
- },
- /** 编辑租户 */
- handleView(row) {
- getCompanyInfo(row.id).then(response => {
- this.viewForm = response.data
- if (this.viewForm.companyNum == null) {
- this.$set(this.viewForm, 'companyNum', 1)
- }
- if (this.viewForm.accountNum == null) {
- this.$set(this.viewForm, 'accountNum', 1)
- }
- // 为 el-switch 提供 number 类型的 status
- this.$set(this.viewForm, 'statusBool', this.viewForm.status)
- this.viewOpen = true
- this.$nextTick(() => {
- if (this.$refs.editForm) this.$refs.editForm.clearValidate()
- })
- }).catch(() => {
- this.$message.error('获取租户信息失败')
- })
- },
- /** 提交编辑租户 */
- submitEdit() {
- this.$refs['editForm'].validate(valid => {
- if (!valid) return
- this.editSubmitting = true
- const data = {
- id: this.viewForm.id,
- tenantName: this.viewForm.tenantName,
- contactName: this.viewForm.contactName,
- contactPhone: this.viewForm.contactPhone,
- expireTime: this.viewForm.expireTime,
- status: this.viewForm.statusBool,
- proxyId: this.viewForm.proxyId,
- companyNum: this.viewForm.companyNum,
- accountNum: this.viewForm.accountNum
- }
- updateTenant(data).then(() => {
- this.$message.success('保存成功')
- this.viewOpen = false
- this.getList()
- }).catch(() => {
- this.$message.error('保存失败,请重试')
- }).finally(() => {
- this.editSubmitting = false
- })
- })
- },
- /** 充值/扣款 */
- handleRecharge(row) {
- this.rechargeForm = {
- companyId: row.id,
- companyName: row.tenantName,
- currentBalance: row.balance || 0,
- operateType: 'recharge',
- amount: null,
- remark: ''
- }
- this.rechargeTitle = `充值/扣款 - ${row.tenantName}`
- this.rechargeOpen = true
- },
- submitRecharge() {
- this.$refs['rechargeForm'].validate(valid => {
- if (!valid) return
- const { companyId, operateType, amount, remark } = this.rechargeForm
- rechargeCompany(companyId, { operateType, amount, remark }).then(res => {
- if (res.code === 200) {
- this.$message.success(res.msg || (operateType === 'recharge' ? '充值成功' : '扣款成功'))
- this.rechargeOpen = false
- this.getList()
- } else {
- this.$message.error(res.msg || '操作失败')
- }
- }).catch(() => {
- this.$message.error('操作失败,请重试')
- })
- })
- },
- /** 禁用租户 */
- handleDisable(row) {
- this.$confirm('确定禁用租户 [' + row.tenantName + '] 吗?', '提示', {
- confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning'
- }).then(() => {
- disableCompany(row.id).then(() => {
- this.$message.success('禁用成功')
- this.getList()
- })
- })
- },
- /** 启用租户 */
- handleEnable(row) {
- this.$confirm('确定启用租户 [' + row.tenantName + '] 吗?', '提示', {
- confirmButtonText: '确定', cancelButtonText: '取消', type: 'info'
- }).then(() => {
- enableCompany(row.id).then(() => {
- this.$message.success('启用成功')
- this.getList()
- })
- })
- },
- handleExport() {
- this.exportLoading = true
- exportCompany(this.queryParams).then(response => {
- this.download(response.msg)
- this.exportLoading = false
- }).catch(() => { this.exportLoading = false })
- },
- normalizeMenuId(id) {
- if (id == null || id === '') return null
- const n = Number(id)
- return Number.isNaN(n) ? id : n
- },
- collectAssignedMenuIds(nodes) {
- const ids = []
- const walk = (list) => {
- (list || []).forEach(n => {
- if (n.visible === '0') {
- const mid = this.normalizeMenuId(n.menuId)
- if (mid != null) ids.push(mid)
- }
- if (n.children && n.children.length) walk(n.children)
- })
- }
- walk(nodes)
- return ids
- },
- applyMenuTreeChecked(menus) {
- const tree = this.$refs.menuTree
- const d = this.menuDialog
- if (!tree) return
- // 勿用 setCheckedKeys(父节点):会级联勾选未分配子节点,导致提交 diff 为空
- tree.setCheckedKeys([])
- this.collectAssignedMenuIds(menus).forEach(menuId => {
- const node = tree.getNode(menuId)
- if (node) node.setChecked(true, false)
- })
- const keys = tree.getCheckedKeys().concat(tree.getHalfCheckedKeys()).map(id => this.normalizeMenuId(id))
- d.checkedKeys = keys
- d.checkedKeySet = new Set(keys)
- },
- walkMenuTreeNodes(nodes, fn) {
- (nodes || []).forEach(n => {
- fn(n)
- if (n.children && n.children.length) this.walkMenuTreeNodes(n.children, fn)
- })
- },
- collectAllMenuIds(nodes) {
- const ids = []
- this.walkMenuTreeNodes(nodes, n => {
- const mid = this.normalizeMenuId(n.menuId)
- if (mid != null) ids.push(mid)
- })
- return ids
- },
- checkAllMenuNodes() {
- const tree = this.$refs.menuTree
- if (!tree) return
- tree.setCheckedKeys(this.collectAllMenuIds(this.menuDialog.treeData))
- },
- uncheckAllMenuNodes() {
- const tree = this.$refs.menuTree
- if (!tree) return
- tree.setCheckedKeys([])
- },
- expandAllMenuNodes() {
- const tree = this.$refs.menuTree
- if (!tree) return
- this.walkMenuTreeNodes(this.menuDialog.treeData, n => {
- const node = tree.getNode(n.menuId)
- if (node) node.expanded = true
- })
- },
- collapseAllMenuNodes() {
- const tree = this.$refs.menuTree
- if (!tree) return
- this.walkMenuTreeNodes(this.menuDialog.treeData, n => {
- const node = tree.getNode(n.menuId)
- if (node) node.expanded = false
- })
- },
- onMenuDialogOpened() {
- if (this.menuDialog.companyId) {
- this.loadTenantMenuTree()
- }
- },
- loadTenantMenuTree() {
- const d = this.menuDialog
- if (!d.companyId) return
- d.loading = true
- d.treeReady = false
- d.treeData = []
- getTenantMenuTree(d.companyId, d.flag).then(res => {
- if (res.code !== 200) {
- return Promise.reject(new Error(res.msg || '加载菜单失败'))
- }
- const flat = res.menus || []
- d.initialAssignedIds = new Set(
- flat.filter(m => m.visible === '0').map(m => this.normalizeMenuId(m.menuId)).filter(id => id != null)
- )
- // 与模板维护页相同:扁平数据 + handleTree(menuId) 建树,保证层级一致
- const tree = this.handleTree(flat, 'menuId')
- d.treeData = tree
- requestAnimationFrame(() => {
- d.treeReady = true
- this.$nextTick(() => this.applyMenuTreeChecked(tree))
- })
- }).catch(err => {
- this.$message.error(err.message || '加载菜单失败')
- }).finally(() => {
- d.loading = false
- })
- },
- /** 编辑菜单(管理端/销售端) */
- handleEditMenu(row, flag) {
- this.menuDialog = {
- visible: true,
- title: (flag === 'sys' ? '编辑管理端菜单 - ' : '编辑销售菜单 - ') + row.tenantName,
- flag: flag,
- companyId: row.id,
- companyName: row.tenantName,
- treeData: [],
- treeReady: false,
- checkedKeys: [],
- checkedKeySet: null,
- initialAssignedIds: null,
- loading: false,
- submitting: false
- }
- },
- /** 模块定价 */
- handleModulePricing(row) {
- this.pricingDialog = {
- visible: true,
- tenantId: row.id,
- tenantName: row.tenantName,
- loading: true,
- submitting: false,
- modules: []
- }
- // 从收费配置API获取启用的模块列表(排除手拨外呼serviceType=6)
- Promise.all([
- listServiceCost(),
- listTrafficPricing({ tenantId: row.id, pageSize: 100 })
- ]).then(([costRes, pricingRes]) => {
- const costList = (costRes.data || []).filter(c => c.enabled === 1 && c.serviceType !== 6)
- const tenantPricings = pricingRes.rows || []
- const pricingMap = {}
- tenantPricings.forEach(p => { pricingMap[p.serviceType] = p })
- this.pricingDialog.modules = costList.map(c => {
- const existing = pricingMap[c.serviceType]
- return {
- serviceType: c.serviceType,
- moduleName: c.configName,
- unit: c.feeUnit,
- globalPrice: c.feeStandard,
- globalCost: c.platformCost,
- price: existing ? existing.price : null,
- costPrice: existing ? existing.costPrice : null,
- status: existing ? existing.status : 1,
- id: existing ? existing.id : null
- }
- })
- this.pricingDialog.loading = false
- }).catch(() => {
- this.pricingDialog.loading = false
- this.$message.error('加载定价数据失败')
- })
- },
- /** 提交模块定价 */
- submitModulePricing() {
- this.pricingDialog.submitting = true
- const tenantId = this.pricingDialog.tenantId
- const promises = this.pricingDialog.modules.map(m => {
- const data = {
- tenantId: tenantId,
- serviceType: m.serviceType,
- price: m.price,
- costPrice: m.costPrice,
- status: m.status
- }
- if (m.id) {
- data.id = m.id
- return updateTrafficPricing(data)
- } else {
- return addTrafficPricing(data)
- }
- })
- Promise.all(promises).then(() => {
- this.$message.success('模块定价保存成功')
- this.pricingDialog.visible = false
- }).catch(() => {
- this.$message.error('部分定价保存失败,请重试')
- }).finally(() => {
- this.pricingDialog.submitting = false
- })
- },
- /** 模块定价 */
- handleModulePricing(row) {
- this.pricingDialog = {
- visible: true,
- tenantId: row.id,
- tenantName: row.tenantName,
- loading: true,
- submitting: false,
- modules: []
- }
- // 从收费配置API获取启用的模块列表(排除手拨外呼serviceType=6)
- Promise.all([
- listServiceCost(),
- listTrafficPricing({ tenantId: row.id, pageSize: 100 })
- ]).then(([costRes, pricingRes]) => {
- const costList = (costRes.data || []).filter(c => c.enabled === 1 && c.serviceType !== 6)
- const tenantPricings = pricingRes.rows || []
- const pricingMap = {}
- tenantPricings.forEach(p => { pricingMap[p.serviceType] = p })
- this.pricingDialog.modules = costList.map(c => {
- const existing = pricingMap[c.serviceType]
- return {
- serviceType: c.serviceType,
- moduleName: c.configName,
- unit: c.feeUnit,
- globalPrice: c.feeStandard,
- globalCost: c.platformCost,
- price: existing ? existing.price : null,
- costPrice: existing ? existing.costPrice : null,
- status: existing ? existing.status : 1,
- id: existing ? existing.id : null
- }
- })
- this.pricingDialog.loading = false
- }).catch(() => {
- this.pricingDialog.loading = false
- this.$message.error('加载定价数据失败')
- })
- },
- /** 提交模块定价 */
- submitModulePricing() {
- this.pricingDialog.submitting = true
- const tenantId = this.pricingDialog.tenantId
- const promises = this.pricingDialog.modules.map(m => {
- const data = {
- tenantId: tenantId,
- serviceType: m.serviceType,
- price: m.price,
- costPrice: m.costPrice,
- status: m.status
- }
- if (m.id) {
- data.id = m.id
- return updateTrafficPricing(data)
- } else {
- return addTrafficPricing(data)
- }
- })
- Promise.all(promises).then(() => {
- this.$message.success('模块定价保存成功')
- this.pricingDialog.visible = false
- }).catch(() => {
- this.$message.error('部分定价保存失败,请重试')
- }).finally(() => {
- this.pricingDialog.submitting = false
- })
- },
- /** 提交菜单编辑 */
- submitMenuEdit() {
- const tree = this.$refs.menuTree
- if (!tree) return
- const initialAssigned = this.menuDialog.initialAssignedIds || new Set()
- const currentSelected = tree.getCheckedKeys()
- .concat(tree.getHalfCheckedKeys())
- .map(id => this.normalizeMenuId(id))
- .filter(id => id != null)
- const currentSet = new Set(currentSelected)
- const selected = currentSelected.filter(id => !initialAssigned.has(id))
- const unSelected = [...initialAssigned].filter(id => !currentSet.has(id))
- if (selected.length === 0 && unSelected.length === 0) {
- this.$message.info('菜单无变化')
- this.menuDialog.visible = false
- return
- }
- this.menuDialog.submitting = true
- editTenantMenu(this.menuDialog.companyId, {
- flag: this.menuDialog.flag,
- selected: selected,
- unSelected: unSelected
- }).then(res => {
- if (res.code === 200) {
- this.$message.success('菜单更新成功')
- this.menuDialog.visible = false
- } else {
- this.$message.error(res.msg || '菜单更新失败')
- }
- }).catch(() => {
- this.$message.error('菜单更新失败')
- }).finally(() => {
- this.menuDialog.submitting = false
- })
- }
- }
- }
- </script>
- <style scoped>
- .mb8 { margin-bottom: 8px; }
- .mb16 { margin-bottom: 16px; }
- .filter-card { padding-bottom: 0; }
- .menu-tree-scroll {
- min-height: 120px;
- max-height: 420px;
- overflow-y: auto;
- }
- .menu-tree-toolbar {
- margin-bottom: 8px;
- display: flex;
- flex-wrap: wrap;
- align-items: center;
- justify-content: flex-end;
- gap: 4px;
- }
- .custom-tree-node {
- display: flex;
- align-items: center;
- font-size: 13px;
- }
- .tenant-dialog-form ::v-deep .el-form-item__label {
- white-space: nowrap;
- line-height: 32px;
- }
- .tenant-dialog-form ::v-deep .el-form-item {
- margin-bottom: 16px;
- }
- .tenant-dialog-form ::v-deep .el-input-number {
- width: 100%;
- }
- .tenant-form-label {
- display: inline-block;
- max-width: 100%;
- overflow: hidden;
- text-overflow: ellipsis;
- vertical-align: middle;
- }
- .tenant-balance-hint {
- color: #999;
- font-size: 12px;
- margin-left: 8px;
- }
- .tenant-balance-item ::v-deep .el-form-item__content {
- line-height: 32px;
- }
- .tenant-status-item ::v-deep .el-form-item__content {
- line-height: 32px;
- }
- </style>
|