|
|
@@ -303,41 +303,89 @@
|
|
|
</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>
|
|
|
+ <el-dialog :title="'模块定价 - ' + pricingDialog.tenantName" :visible.sync="pricingDialog.visible" width="720px" append-to-body destroy-on-close class="pricing-dialog">
|
|
|
+ <div v-loading="pricingDialog.loading" class="pricing-container">
|
|
|
+ <!-- 头部提示 -->
|
|
|
+ <div class="pricing-header">
|
|
|
+ <i class="el-icon-info" style="color:#409EFF;margin-right:6px"></i>
|
|
|
+ <span>租户售价/成本价留空或填0则使用全局定价</span>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 模块卡片列表 -->
|
|
|
+ <div class="pricing-list">
|
|
|
+ <div v-for="(item, index) in pricingDialog.modules" :key="item.serviceType" class="pricing-card">
|
|
|
+ <!-- 卡片头部 -->
|
|
|
+ <div class="pricing-card-header">
|
|
|
+ <div class="module-title">
|
|
|
+ <el-tag size="small" type="primary" effect="plain">{{ item.moduleName }}</el-tag>
|
|
|
+ </div>
|
|
|
+ <el-switch
|
|
|
+ v-model="item.status"
|
|
|
+ :active-value="1"
|
|
|
+ :inactive-value="0"
|
|
|
+ active-text="启用"
|
|
|
+ inactive-text="禁用"
|
|
|
+ size="small"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 定价区域 -->
|
|
|
+ <div class="pricing-card-body">
|
|
|
+ <!-- 全局定价参考 -->
|
|
|
+ <div class="global-pricing">
|
|
|
+ <div class="price-item">
|
|
|
+ <span class="price-label">全局售价</span>
|
|
|
+ <span class="price-value global">¥{{ item.globalPrice != null ? item.globalPrice : '-' }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="price-item">
|
|
|
+ <span class="price-label">全局成本</span>
|
|
|
+ <span class="price-value global">¥{{ item.globalCost != null ? item.globalCost : '-' }}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="divider"></div>
|
|
|
+
|
|
|
+ <!-- 租户自定义定价 -->
|
|
|
+ <div class="tenant-pricing">
|
|
|
+ <div class="price-item">
|
|
|
+ <span class="price-label">
|
|
|
+ <i class="el-icon-price-tag" style="color:#67C23A;margin-right:4px"></i>租户售价
|
|
|
+ </span>
|
|
|
+ <el-input-number
|
|
|
+ v-model="item.price"
|
|
|
+ :precision="4"
|
|
|
+ :min="0"
|
|
|
+ :step="0.01"
|
|
|
+ size="small"
|
|
|
+ controls-position="right"
|
|
|
+ style="width:140px"
|
|
|
+ placeholder="跟随全局"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ <div class="price-item">
|
|
|
+ <span class="price-label">
|
|
|
+ <i class="el-icon-coin" style="color:#E6A23C;margin-right:4px"></i>租户成本
|
|
|
+ </span>
|
|
|
+ <el-input-number
|
|
|
+ v-model="item.costPrice"
|
|
|
+ :precision="4"
|
|
|
+ :min="0"
|
|
|
+ :step="0.01"
|
|
|
+ size="small"
|
|
|
+ controls-position="right"
|
|
|
+ style="width:140px"
|
|
|
+ placeholder="跟随全局"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </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 slot="footer" class="pricing-footer">
|
|
|
+ <el-button @click="pricingDialog.visible = false" size="medium">取 消</el-button>
|
|
|
+ <el-button type="primary" :loading="pricingDialog.submitting" size="medium" icon="el-icon-check" @click="submitModulePricing">保 存</el-button>
|
|
|
</div>
|
|
|
</el-dialog>
|
|
|
</div>
|
|
|
@@ -361,7 +409,7 @@ export default {
|
|
|
companyList: [],
|
|
|
statusOptions: [
|
|
|
{ value: 1, label: '正常' },
|
|
|
- { value: 0, label: '禁用' }
|
|
|
+ { value: 3, label: '禁用' }
|
|
|
],
|
|
|
queryParams: {
|
|
|
pageNum: 1,
|
|
|
@@ -630,7 +678,16 @@ export default {
|
|
|
handleExport() {
|
|
|
this.exportLoading = true
|
|
|
exportCompany(this.queryParams).then(response => {
|
|
|
- this.download(response.msg)
|
|
|
+ // 直接处理 Blob 下载
|
|
|
+ const blob = new Blob([response], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' })
|
|
|
+ const url = window.URL.createObjectURL(blob)
|
|
|
+ const link = document.createElement('a')
|
|
|
+ link.href = url
|
|
|
+ link.download = '租户列表数据.xlsx'
|
|
|
+ document.body.appendChild(link)
|
|
|
+ link.click()
|
|
|
+ document.body.removeChild(link)
|
|
|
+ window.URL.revokeObjectURL(url)
|
|
|
this.exportLoading = false
|
|
|
}).catch(() => { this.exportLoading = false })
|
|
|
},
|
|
|
@@ -814,14 +871,17 @@ export default {
|
|
|
tenantPricings.forEach(p => { pricingMap[p.serviceType] = p })
|
|
|
this.pricingDialog.modules = costList.map(c => {
|
|
|
const existing = pricingMap[c.serviceType]
|
|
|
+ // 如果租户定价为空或为0,则使用全局定价作为默认值
|
|
|
+ const tenantPrice = existing ? existing.price : null
|
|
|
+ const tenantCostPrice = existing ? existing.costPrice : null
|
|
|
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,
|
|
|
+ price: tenantPrice || c.feeStandard,
|
|
|
+ costPrice: tenantCostPrice || c.platformCost,
|
|
|
status: existing ? existing.status : 1,
|
|
|
id: existing ? existing.id : null
|
|
|
}
|
|
|
@@ -881,14 +941,17 @@ export default {
|
|
|
tenantPricings.forEach(p => { pricingMap[p.serviceType] = p })
|
|
|
this.pricingDialog.modules = costList.map(c => {
|
|
|
const existing = pricingMap[c.serviceType]
|
|
|
+ // 如果租户定价为空或为0,则使用全局定价作为默认值
|
|
|
+ const tenantPrice = existing ? existing.price : null
|
|
|
+ const tenantCostPrice = existing ? existing.costPrice : null
|
|
|
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,
|
|
|
+ price: tenantPrice || c.feeStandard,
|
|
|
+ costPrice: tenantCostPrice || c.platformCost,
|
|
|
status: existing ? existing.status : 1,
|
|
|
id: existing ? existing.id : null
|
|
|
}
|
|
|
@@ -904,11 +967,18 @@ export default {
|
|
|
this.pricingDialog.submitting = true
|
|
|
const tenantId = this.pricingDialog.tenantId
|
|
|
const promises = this.pricingDialog.modules.map(m => {
|
|
|
+ // 空值转为 0(使用全局定价)
|
|
|
+ const price = m.price || 0
|
|
|
+ const costPrice = m.costPrice || 0
|
|
|
+ // 从未设置过且都是 0,跳过
|
|
|
+ if (!m.id && price === 0 && costPrice === 0) {
|
|
|
+ return Promise.resolve()
|
|
|
+ }
|
|
|
const data = {
|
|
|
tenantId: tenantId,
|
|
|
serviceType: m.serviceType,
|
|
|
- price: m.price,
|
|
|
- costPrice: m.costPrice,
|
|
|
+ price: price,
|
|
|
+ costPrice: costPrice,
|
|
|
status: m.status
|
|
|
}
|
|
|
if (m.id) {
|
|
|
@@ -1018,4 +1088,141 @@ export default {
|
|
|
.tenant-status-item ::v-deep .el-form-item__content {
|
|
|
line-height: 32px;
|
|
|
}
|
|
|
+
|
|
|
+/* ===== 模块定价弹窗样式 ===== */
|
|
|
+.pricing-dialog ::v-deep .el-dialog {
|
|
|
+ border-radius: 12px;
|
|
|
+ overflow: hidden;
|
|
|
+}
|
|
|
+.pricing-dialog ::v-deep .el-dialog__header {
|
|
|
+ background: linear-gradient(135deg, #409EFF 0%, #317ecc 100%);
|
|
|
+ padding: 16px 20px;
|
|
|
+}
|
|
|
+.pricing-dialog ::v-deep .el-dialog__title {
|
|
|
+ color: #fff;
|
|
|
+ font-weight: 500;
|
|
|
+}
|
|
|
+.pricing-dialog ::v-deep .el-dialog__headerbtn .el-dialog__close {
|
|
|
+ color: #fff;
|
|
|
+}
|
|
|
+.pricing-dialog ::v-deep .el-dialog__body {
|
|
|
+ padding: 20px;
|
|
|
+ background: #f5f7fa;
|
|
|
+}
|
|
|
+
|
|
|
+.pricing-container {
|
|
|
+ max-height: 60vh;
|
|
|
+ overflow-y: auto;
|
|
|
+}
|
|
|
+
|
|
|
+/* 头部提示 */
|
|
|
+.pricing-header {
|
|
|
+ background: linear-gradient(135deg, #ecf5ff 0%, #f0f9ff 100%);
|
|
|
+ border: 1px solid #d9ecff;
|
|
|
+ border-radius: 8px;
|
|
|
+ padding: 12px 16px;
|
|
|
+ margin-bottom: 16px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ font-size: 13px;
|
|
|
+ color: #409EFF;
|
|
|
+}
|
|
|
+
|
|
|
+/* 卡片列表 */
|
|
|
+.pricing-list {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 12px;
|
|
|
+}
|
|
|
+
|
|
|
+/* 定价卡片 */
|
|
|
+.pricing-card {
|
|
|
+ background: #fff;
|
|
|
+ border-radius: 10px;
|
|
|
+ box-shadow: 0 2px 8px rgba(0,0,0,0.06);
|
|
|
+ border: 1px solid #ebeef5;
|
|
|
+ overflow: hidden;
|
|
|
+ transition: all 0.3s ease;
|
|
|
+}
|
|
|
+.pricing-card:hover {
|
|
|
+ box-shadow: 0 4px 12px rgba(0,0,0,0.1);
|
|
|
+ transform: translateY(-1px);
|
|
|
+}
|
|
|
+
|
|
|
+/* 卡片头部 */
|
|
|
+.pricing-card-header {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ padding: 12px 16px;
|
|
|
+ background: linear-gradient(135deg, #fafbfc 0%, #f5f7fa 100%);
|
|
|
+ border-bottom: 1px solid #ebeef5;
|
|
|
+}
|
|
|
+.module-title .el-tag {
|
|
|
+ font-size: 13px;
|
|
|
+ font-weight: 500;
|
|
|
+ padding: 4px 12px;
|
|
|
+}
|
|
|
+
|
|
|
+/* 卡片主体 */
|
|
|
+.pricing-card-body {
|
|
|
+ padding: 16px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 20px;
|
|
|
+}
|
|
|
+
|
|
|
+/* 定价区域 */
|
|
|
+.global-pricing,
|
|
|
+.tenant-pricing {
|
|
|
+ flex: 1;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 12px;
|
|
|
+}
|
|
|
+
|
|
|
+/* 分割线 */
|
|
|
+.divider {
|
|
|
+ width: 1px;
|
|
|
+ height: 60px;
|
|
|
+ background: linear-gradient(180deg, transparent 0%, #dcdfe6 50%, transparent 100%);
|
|
|
+}
|
|
|
+
|
|
|
+/* 价格项 */
|
|
|
+.price-item {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ gap: 12px;
|
|
|
+}
|
|
|
+.price-label {
|
|
|
+ font-size: 13px;
|
|
|
+ color: #606266;
|
|
|
+ white-space: nowrap;
|
|
|
+}
|
|
|
+.price-value {
|
|
|
+ font-size: 14px;
|
|
|
+ font-weight: 500;
|
|
|
+ font-family: 'Microsoft YaHei', sans-serif;
|
|
|
+}
|
|
|
+.price-value.global {
|
|
|
+ color: #909399;
|
|
|
+ background: #f4f4f5;
|
|
|
+ padding: 4px 12px;
|
|
|
+ border-radius: 4px;
|
|
|
+}
|
|
|
+
|
|
|
+/* 底部按钮 */
|
|
|
+.pricing-footer {
|
|
|
+ display: flex;
|
|
|
+ justify-content: flex-end;
|
|
|
+ gap: 12px;
|
|
|
+ padding: 16px 20px;
|
|
|
+ background: #fff;
|
|
|
+ border-top: 1px solid #ebeef5;
|
|
|
+}
|
|
|
+.pricing-footer .el-button {
|
|
|
+ padding: 10px 24px;
|
|
|
+ border-radius: 6px;
|
|
|
+}
|
|
|
</style>
|