DESKTOP-7E9LH09\admin 22 часов назад
Родитель
Сommit
163e911a3b

Разница между файлами не показана из-за своего большого размера
+ 0 - 0
dist/index.html


+ 18 - 0
src/router/index.js

@@ -654,12 +654,30 @@ export const constantRoutes = [
         name: "agentWithdrawLog",
         meta: { title: "代理提现记录" },
       },
+      {
+        path: "agentRebateLog",
+        component: () => import("@/views/agent/rebateLog/index.vue"),
+        name: "agentRebateLog",
+        meta: { title: "代理返佣记录" },
+      },
       {
         path: "agentReport",
         component: () => import("@/views/agent/report/index.vue"),
         name: "agentReport",
         meta: { title: "代理报表" },
       },
+      {
+        path: "agentTurnoverCommission",
+        component: () => import("@/views/agent/turnoverCommission/index.vue"),
+        name: "agentTurnoverCommission",
+        meta: { title: "代理流水佣金比例" },
+      },
+      {
+        path: "agentProfitCommission",
+        component: () => import("@/views/agent/profitCommission/index.vue"),
+        name: "agentProfitCommission",
+        meta: { title: "代理盈亏佣金比例" },
+      },
       {
         path: "agentLoginLog",
         component: () => import("@/views/agent/loginLog/index.vue"),

+ 162 - 0
src/views/agent/index.vue

@@ -142,6 +142,72 @@
     <el-dialog
       :title="agentDialogTitle"
       :visible.sync="agentDialogVisible"
+      width="52%"
+      top="15vh"
+      @close="resetAgentForm"
+    >
+      <el-form
+        ref="agentForm"
+        :model="agentForm"
+        :rules="agentRules"
+        label-width="120px"
+        size="small"
+        class="agent-dialog-form"
+      >
+        <el-form-item label="代理账号" prop="agent_account">
+          <el-input v-model="agentForm.agent_account" placeholder="请输入代理账号" />
+        </el-form-item>
+        <el-form-item label="登录密码" prop="password">
+          <el-input v-model="agentForm.password" placeholder="请输入登录密码" show-password />
+        </el-form-item>
+        <el-form-item label="取款密码" prop="withdraw_password">
+          <el-input v-model="agentForm.withdraw_password" placeholder="取款密码只能为6位数字" maxlength="6" />
+        </el-form-item>
+        <el-form-item label="代理邀请" prop="invite_code">
+          <div class="invite-code-field">
+            <el-input v-model="agentForm.invite_code" placeholder="邀请码为6位数字与字母" maxlength="6" />
+            <el-button type="primary" @click="generateInviteCode">生成</el-button>
+          </div>
+        </el-form-item>
+        <el-form-item label="盈亏佣金策略" prop="profit_strategy">
+          <el-select v-model="agentForm.profit_strategy" placeholder="选择盈亏佣金策略" class="strategy-select" clearable>
+            <el-option v-for="item in profitStrategies" :key="item.value" :label="item.label" :value="item.value" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="流水佣金策略" prop="turnover_strategy">
+          <el-select v-model="agentForm.turnover_strategy" placeholder="选择流水佣金策略" class="strategy-select" clearable>
+            <el-option v-for="item in turnoverStrategies" :key="item.value" :label="item.label" :value="item.value" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="存款手续费比例" prop="deposit_fee_rate">
+          <div class="rate-field">
+            <el-input v-model="agentForm.deposit_fee_rate" />
+            <span>%</span>
+          </div>
+        </el-form-item>
+        <el-form-item label="取款手续费比例" prop="withdraw_fee_rate">
+          <div class="rate-field">
+            <el-input v-model="agentForm.withdraw_fee_rate" />
+            <span>%</span>
+          </div>
+        </el-form-item>
+        <el-form-item label="加/扣款权限" prop="balance_permission" class="permission-form-item">
+          <el-radio-group v-model="agentForm.balance_permission">
+            <el-radio label="none">无</el-radio>
+            <el-radio label="both">加/扣款</el-radio>
+          </el-radio-group>
+        </el-form-item>
+      </el-form>
+      <div slot="footer">
+        <el-button size="small" @click="agentDialogVisible = false">取消</el-button>
+        <el-button type="primary" size="small" @click="submitAgent">确定</el-button>
+      </div>
+    </el-dialog>
+
+    <el-dialog
+      :title="agentDialogTitle"
+      :visible.sync="legacyAgentDialogVisible"
+      v-if="false"
       width="620px"
       @close="resetAgentForm"
     >
@@ -360,8 +426,18 @@ export default {
       total: 0,
       tableData: [],
       agentDialogVisible: false,
+      legacyAgentDialogVisible: false,
       agentDialogType: 'add',
       agentForm: this.getDefaultAgentForm(),
+      profitStrategies: [
+        { value: '30', label: '30' },
+        { value: '一级代理', label: '一级代理' },
+        { value: '盈亏-官方默认比例', label: '盈亏-官方默认比例' }
+      ],
+      turnoverStrategies: [
+        { value: '流水-默认比例', label: '流水-默认比例' },
+        { value: '一级代理', label: '一级代理' }
+      ],
       agentRules: {
         agent_account: [
           { required: true, message: '请输入代理账号', trigger: 'blur' }
@@ -466,12 +542,16 @@ export default {
         id: '',
         agent_account: '',
         password: '',
+        withdraw_password: '',
         invite_code: '',
         real_name: '',
         parent_agent: '',
         balance: 0,
+        profit_strategy: '',
+        turnover_strategy: '',
         deposit_fee_rate: 0,
         withdraw_fee_rate: 0,
+        balance_permission: 'none',
         status: 1
       }
     },
@@ -579,6 +659,14 @@ export default {
       this.page = val
       this.getList()
     },
+    generateInviteCode() {
+      const chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789'
+      let code = ''
+      for (let i = 0; i < 6; i += 1) {
+        code += chars.charAt(Math.floor(Math.random() * chars.length))
+      }
+      this.agentForm.invite_code = code
+    },
     tableRowClassName({ rowIndex }) {
       return rowIndex % 2 === 1 ? 'agent-row-tint' : ''
     },
@@ -609,6 +697,20 @@ export default {
       })
     },
     submitAgent() {
+      if (!/^\d{6}$/.test(String(this.agentForm.withdraw_password || ''))) {
+        this.$message.error('取款密码只能为6位数字')
+        return
+      }
+      if (this.agentForm.invite_code && !/^[A-Za-z0-9]{6}$/.test(this.agentForm.invite_code)) {
+        this.$message.error('邀请码为6位数字与字母')
+        return
+      }
+      const depositRate = Number(this.agentForm.deposit_fee_rate)
+      const withdrawRate = Number(this.agentForm.withdraw_fee_rate)
+      if (Number.isNaN(depositRate) || depositRate < 0 || depositRate > 100 || Number.isNaN(withdrawRate) || withdrawRate < 0 || withdrawRate > 100) {
+        this.$message.error('手续费比例请输入0-100之间的数值')
+        return
+      }
       this.$refs.agentForm.validate(valid => {
         if (!valid) return
         if (this.agentDialogType === 'add') {
@@ -868,6 +970,66 @@ export default {
   text-align: right;
 }
 
+.agent-dialog-form {
+  padding: 4px 16px 0;
+
+  ::v-deep .el-form-item {
+    margin-bottom: 24px;
+  }
+
+  ::v-deep .el-form-item__label {
+    color: #606266;
+  }
+
+  ::v-deep .el-input__inner {
+    height: 36px;
+    line-height: 36px;
+  }
+}
+
+.invite-code-field {
+  display: flex;
+  align-items: center;
+
+  .el-input {
+    flex: 1;
+  }
+
+  .el-button {
+    margin-left: 0;
+    border-radius: 0 4px 4px 0;
+  }
+}
+
+.strategy-select {
+  width: 235px;
+}
+
+.rate-field {
+  display: flex;
+  align-items: stretch;
+
+  .el-input {
+    flex: 1;
+  }
+
+  > span {
+    display: flex;
+    width: 56px;
+    align-items: center;
+    justify-content: center;
+    color: #909399;
+    background: #f5f7fa;
+    border: 1px solid #dcdfe6;
+    border-left: 0;
+    border-radius: 0 4px 4px 0;
+  }
+}
+
+.permission-form-item {
+  margin-bottom: 0 !important;
+}
+
 .full-input {
   width: 100%;
 }

+ 333 - 0
src/views/agent/profitCommission/index.vue

@@ -0,0 +1,333 @@
+<template>
+  <div class="agent-profit-commission-page">
+    <div class="commission-panel">
+      <div class="panel-toolbar">
+        <el-button type="primary" icon="el-icon-plus" size="small" @click="handleAdd">新增</el-button>
+      </div>
+
+      <el-table v-loading="loading" :data="pagedRows" border class="commission-table" height="calc(100vh - 225px)">
+        <el-table-column type="index" label="序号" width="135" align="center" />
+        <el-table-column prop="name" label="盈亏比例名称" width="180" align="center" />
+        <el-table-column prop="casino" label="真人盈亏比例" min-width="145" align="center">
+          <template slot-scope="{ row }">{{ formatRate(row.casino) }}</template>
+        </el-table-column>
+        <el-table-column prop="electronics" label="电子盈亏比例" min-width="145" align="center">
+          <template slot-scope="{ row }">{{ formatRate(row.electronics) }}</template>
+        </el-table-column>
+        <el-table-column prop="lottery" label="彩票盈亏比例" min-width="145" align="center">
+          <template slot-scope="{ row }">{{ formatRate(row.lottery) }}</template>
+        </el-table-column>
+        <el-table-column prop="sports" label="体育盈亏比例" min-width="145" align="center">
+          <template slot-scope="{ row }">{{ formatRate(row.sports) }}</template>
+        </el-table-column>
+        <el-table-column prop="esports" label="电竞盈亏比例" min-width="145" align="center">
+          <template slot-scope="{ row }">{{ formatRate(row.esports) }}</template>
+        </el-table-column>
+        <el-table-column prop="fishing" label="捕鱼盈亏比例" min-width="145" align="center">
+          <template slot-scope="{ row }">{{ formatRate(row.fishing) }}</template>
+        </el-table-column>
+        <el-table-column prop="chess" label="棋牌盈亏比例" min-width="145" align="center">
+          <template slot-scope="{ row }">{{ formatRate(row.chess) }}</template>
+        </el-table-column>
+        <el-table-column label="是否默认" width="130" align="center">
+          <template slot-scope="{ row }">
+            <el-tag v-if="row.is_default" type="success" size="mini" effect="plain">是</el-tag>
+            <el-tag v-else type="info" size="mini" effect="plain">否</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column prop="created_at" label="创建时间" width="180" align="center" />
+        <el-table-column label="操作" width="150" fixed="right" align="center">
+          <template slot-scope="{ row }">
+            <el-button type="text" size="mini" @click="handleEdit(row)">修改</el-button>
+            <el-button type="text" size="mini" class="danger-action" @click="handleDelete(row)">删除</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <el-pagination
+        background
+        class="commission-pagination"
+        layout="total, sizes, prev, pager, next, jumper"
+        :current-page="page"
+        :page-size="limit"
+        :page-sizes="[10, 20, 50, 100]"
+        :total="rows.length"
+        @size-change="handleSizeChange"
+        @current-change="handleCurrentChange"
+      />
+    </div>
+
+    <el-dialog
+      :title="dialogType === 'add' ? '新增' : '修改'"
+      :visible.sync="dialogVisible"
+      width="50%"
+      top="15vh"
+      @close="resetForm"
+    >
+      <el-form ref="commissionForm" :model="form" :rules="rules" label-width="120px" size="small" class="commission-form">
+        <el-form-item label="盈亏比例名称" prop="name">
+          <el-input v-model="form.name" maxlength="30" placeholder="盈亏比例名称" />
+        </el-form-item>
+        <el-form-item v-for="field in rateFields" :key="field.prop" :label="field.label" :prop="field.prop">
+          <div class="rate-control">
+            <el-input v-model="form[field.prop]" :placeholder="field.placeholder" />
+            <span class="unit">%</span>
+          </div>
+        </el-form-item>
+        <div class="default-tip">
+          <span>注意:</span>
+          <strong>默认比例仅存在一条,如果勾选默认会覆盖掉已配置过此比例的代理</strong>
+        </div>
+        <el-form-item label="是否默认" prop="is_default" class="default-form-item">
+          <el-radio-group v-model="form.is_default">
+            <el-radio :label="false">否</el-radio>
+            <el-radio :label="true">是</el-radio>
+          </el-radio-group>
+        </el-form-item>
+      </el-form>
+      <div slot="footer">
+        <el-button size="small" @click="dialogVisible = false">取消</el-button>
+        <el-button type="primary" size="small" @click="submitForm">确定</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+const emptyForm = () => ({
+  id: null,
+  name: '',
+  casino: '',
+  electronics: '',
+  lottery: '',
+  sports: '',
+  esports: '',
+  fishing: '',
+  chess: '',
+  is_default: false
+})
+
+export default {
+  name: 'AgentProfitCommission',
+  data() {
+    return {
+      loading: false,
+      page: 1,
+      limit: 10,
+      dialogVisible: false,
+      dialogType: 'add',
+      form: emptyForm(),
+      rows: [
+        { id: 1, name: '30', casino: 30, electronics: 30, lottery: 30, sports: 30, esports: 30, fishing: 30, chess: 30, is_default: true, created_at: '2026-05-29 19:10:00' },
+        { id: 2, name: '一级代理', casino: 5, electronics: 0, lottery: 0, sports: 0, esports: 0, fishing: 0, chess: 0, is_default: false, created_at: '2025-12-02 23:11:59' },
+        { id: 3, name: '盈亏-官方默认比例', casino: 20, electronics: 10, lottery: 0, sports: 0, esports: 0, fishing: 0, chess: 0, is_default: false, created_at: '2024-11-14 09:02:17' }
+      ],
+      rateFields: [
+        { prop: 'casino', label: '真人盈亏比例', placeholder: '真人盈亏比例' },
+        { prop: 'electronics', label: '电子盈亏比例', placeholder: '电子盈亏比例' },
+        { prop: 'lottery', label: '彩票盈亏比例', placeholder: '彩票盈亏比例' },
+        { prop: 'sports', label: '体育盈亏比例', placeholder: '体育盈亏比例' },
+        { prop: 'esports', label: '电竞盈亏比例', placeholder: '电竞盈亏比例' },
+        { prop: 'fishing', label: '捕鱼盈亏比例', placeholder: '捕鱼盈亏比例' },
+        { prop: 'chess', label: '棋牌盈亏比例', placeholder: '棋牌盈亏比例' }
+      ],
+      rules: {
+        name: [{ required: true, message: '请输入盈亏比例名称', trigger: 'blur' }],
+        casino: [{ required: true, message: '请输入真人盈亏比例', trigger: 'blur' }, { validator: this.validateRate, trigger: 'blur' }],
+        electronics: [{ required: true, message: '请输入电子盈亏比例', trigger: 'blur' }, { validator: this.validateRate, trigger: 'blur' }],
+        lottery: [{ required: true, message: '请输入彩票盈亏比例', trigger: 'blur' }, { validator: this.validateRate, trigger: 'blur' }],
+        sports: [{ required: true, message: '请输入体育盈亏比例', trigger: 'blur' }, { validator: this.validateRate, trigger: 'blur' }],
+        esports: [{ required: true, message: '请输入电竞盈亏比例', trigger: 'blur' }, { validator: this.validateRate, trigger: 'blur' }],
+        fishing: [{ required: true, message: '请输入捕鱼盈亏比例', trigger: 'blur' }, { validator: this.validateRate, trigger: 'blur' }],
+        chess: [{ required: true, message: '请输入棋牌盈亏比例', trigger: 'blur' }, { validator: this.validateRate, trigger: 'blur' }]
+      }
+    }
+  },
+  computed: {
+    pagedRows() {
+      const start = (this.page - 1) * this.limit
+      return this.rows.slice(start, start + this.limit)
+    }
+  },
+  methods: {
+    validateRate(rule, value, callback) {
+      const rate = Number(value)
+      if (value === '' || Number.isNaN(rate) || rate < 0 || rate > 100) {
+        callback(new Error('请输入0-100之间的比例'))
+        return
+      }
+      callback()
+    },
+    formatRate(value) {
+      return Number(value || 0).toFixed(2).replace(/\.00$/, '')
+    },
+    handleAdd() {
+      this.dialogType = 'add'
+      this.form = emptyForm()
+      this.dialogVisible = true
+    },
+    handleEdit(row) {
+      this.dialogType = 'edit'
+      this.form = { ...row }
+      this.dialogVisible = true
+    },
+    handleDelete(row) {
+      if (row.is_default) {
+        this.$message.warning('默认比例不能删除')
+        return
+      }
+      this.$confirm('确定删除该盈亏佣金比例吗?', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        this.rows = this.rows.filter(item => item.id !== row.id)
+        this.$message.success('删除成功')
+        if (this.page > 1 && this.pagedRows.length === 0) this.page -= 1
+      }).catch(() => {})
+    },
+    submitForm() {
+      this.$refs.commissionForm.validate(valid => {
+        if (!valid) return
+        const data = { ...this.form }
+        Object.keys(data).forEach(key => {
+          if (key !== 'name' && key !== 'id' && key !== 'created_at' && key !== 'is_default') data[key] = data[key] === '' ? 0 : Number(data[key])
+        })
+        if (data.is_default) this.rows.forEach(item => { item.is_default = false })
+        if (this.dialogType === 'add') {
+          data.id = Date.now()
+          data.created_at = this.formatDate(new Date())
+          this.rows.push(data)
+        } else {
+          const index = this.rows.findIndex(item => item.id === data.id)
+          if (index > -1) this.$set(this.rows, index, data)
+        }
+        this.dialogVisible = false
+        this.$message.success(this.dialogType === 'add' ? '新增成功' : '修改成功')
+      })
+    },
+    resetForm() {
+      if (this.$refs.commissionForm) this.$refs.commissionForm.resetFields()
+      this.form = emptyForm()
+    },
+    formatDate(date) {
+      const pad = value => String(value).padStart(2, '0')
+      return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`
+    },
+    handleSizeChange(value) {
+      this.limit = value
+      this.page = 1
+    },
+    handleCurrentChange(value) {
+      this.page = value
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.agent-profit-commission-page {
+  width: 100%;
+  height: 100%;
+  padding: 16px;
+  overflow: hidden;
+  background: #f3f6f8;
+}
+
+.commission-panel {
+  height: 100%;
+  padding: 20px 20px 12px;
+  overflow: hidden;
+  background: #fff;
+  box-shadow: 0 1px 8px rgba(0, 0, 0, 0.08);
+}
+
+.panel-toolbar {
+  height: 44px;
+}
+
+.commission-table {
+  width: 100%;
+
+  ::v-deep th {
+    height: 44px;
+    color: #7b8794;
+    font-weight: 600;
+    background: #fff;
+  }
+
+  ::v-deep td {
+    height: 45px;
+    color: #334155;
+  }
+
+  ::v-deep .el-tag--success {
+    color: #13b6ad;
+    background: #e9fbfb;
+    border-color: #b7eeee;
+  }
+}
+
+.commission-pagination {
+  padding-top: 12px;
+  text-align: right;
+}
+
+.commission-form {
+  padding: 4px 16px 0;
+
+  ::v-deep .el-form-item {
+    margin-bottom: 24px;
+  }
+
+  ::v-deep .el-form-item__label {
+    color: #606266;
+  }
+
+  ::v-deep .el-input__inner {
+    height: 36px;
+    line-height: 36px;
+  }
+}
+
+.rate-control {
+  display: flex;
+  align-items: stretch;
+
+  .el-input {
+    flex: 1;
+  }
+}
+
+.unit {
+  display: flex;
+  width: 56px;
+  align-items: center;
+  justify-content: center;
+  color: #909399;
+  background: #f5f7fa;
+  border: 1px solid #dcdfe6;
+  border-left: 0;
+  border-radius: 0 4px 4px 0;
+}
+
+.default-tip {
+  margin: -2px 0 24px 76px;
+  color: #606266;
+  line-height: 24px;
+
+  strong {
+    margin-left: 10px;
+    color: #f5222d;
+    font-size: 16px;
+  }
+}
+
+.default-form-item {
+  margin-bottom: 0 !important;
+}
+
+.danger-action {
+  color: #f56c6c;
+}
+</style>

+ 289 - 0
src/views/agent/rebateLog/index.vue

@@ -0,0 +1,289 @@
+<template>
+  <div class="agent-rebate-page">
+    <div class="rebate-panel">
+      <el-form ref="queryForm" :model="query" inline size="small" class="rebate-search">
+        <el-form-item label="代理账号" prop="agent_account">
+          <el-input v-model="query.agent_account" placeholder="代理账号" clearable />
+        </el-form-item>
+        <el-form-item label="流水佣金状态" prop="turnover_status">
+          <el-select v-model="query.turnover_status" placeholder="流水佣金状态" clearable>
+            <el-option label="待返佣" value="pending" />
+            <el-option label="已返佣" value="settled" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="盈亏佣金状态" prop="profit_status">
+          <el-select v-model="query.profit_status" placeholder="盈亏佣金状态" clearable>
+            <el-option label="待返佣" value="pending" />
+            <el-option label="已返佣" value="settled" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="创建日期" prop="date_range">
+          <el-date-picker
+            v-model="query.date_range"
+            type="datetimerange"
+            range-separator="-"
+            start-placeholder="开始日期"
+            end-placeholder="结束日期"
+            value-format="yyyy-MM-dd HH:mm:ss"
+            :default-time="['00:00:00', '23:59:59']"
+            class="date-range"
+          />
+        </el-form-item>
+        <el-form-item class="search-actions">
+          <el-button @click="handleSearch">查询</el-button>
+          <el-button type="danger" :disabled="selectedRows.length === 0" @click="handleBatchRebate">批量流水返佣</el-button>
+        </el-form-item>
+      </el-form>
+
+      <el-table
+        ref="rebateTable"
+        v-loading="loading"
+        :data="pagedRows"
+        border
+        height="calc(100vh - 275px)"
+        class="rebate-table"
+        @selection-change="handleSelectionChange"
+      >
+        <el-table-column type="selection" width="100" align="center" />
+        <el-table-column prop="agent_account" label="代理账号" width="150" align="center" />
+        <el-table-column prop="rebate_no" label="代理返佣单号" width="200" align="center" />
+        <el-table-column prop="platform" label="游戏平台" width="150" align="center" />
+        <el-table-column prop="turnover_commission" label="流水佣金" width="150" align="center">
+          <template slot="header">
+            流水佣金
+            <el-tooltip content="根据代理流水和流水佣金比例计算" placement="top">
+              <i class="el-icon-question table-help" />
+            </el-tooltip>
+          </template>
+          <template slot-scope="{ row }">{{ formatMoney(row.turnover_commission) }}</template>
+        </el-table-column>
+        <el-table-column prop="turnover_ratio" label="流水佣金比例" width="150" align="center">
+          <template slot-scope="{ row }">{{ formatRate(row.turnover_ratio) }}</template>
+        </el-table-column>
+        <el-table-column prop="profit_commission" label="盈亏佣金" width="150" align="center">
+          <template slot-scope="{ row }">{{ formatMoney(row.profit_commission) }}</template>
+        </el-table-column>
+        <el-table-column prop="profit_ratio" label="盈亏佣金比例" width="150" align="center">
+          <template slot-scope="{ row }">{{ formatRate(row.profit_ratio) }}</template>
+        </el-table-column>
+        <el-table-column prop="bet_count" label="注单数量" width="130" align="center" />
+        <el-table-column prop="bet_amount" label="总投注金额" width="150" align="center">
+          <template slot-scope="{ row }">{{ formatMoney(row.bet_amount) }}</template>
+        </el-table-column>
+        <el-table-column label="操作" width="150" fixed="right" align="center">
+          <template slot-scope="{ row }">
+            <el-button v-if="row.turnover_status === 'pending'" type="text" size="mini" @click="handleRebate(row)">流水返佣</el-button>
+            <span v-else class="empty-action">--</span>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <el-pagination
+        background
+        class="rebate-pagination"
+        layout="total, sizes, prev, pager, next, jumper"
+        :current-page="page"
+        :page-size="limit"
+        :page-sizes="[10, 20, 50, 100]"
+        :total="filteredRows.length"
+        @size-change="handleSizeChange"
+        @current-change="handleCurrentChange"
+      />
+    </div>
+  </div>
+</template>
+
+<script>
+const getTodayRange = () => {
+  const now = new Date()
+  const pad = value => String(value).padStart(2, '0')
+  const date = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}`
+  return [`${date} 00:00:00`, `${date} 23:59:59`]
+}
+
+export default {
+  name: 'AgentRebateLog',
+  data() {
+    return {
+      loading: false,
+      page: 1,
+      limit: 10,
+      query: {
+        agent_account: '',
+        turnover_status: '',
+        profit_status: '',
+        date_range: getTodayRange()
+      },
+      rows: [],
+      selectedRows: []
+    }
+  },
+  computed: {
+    filteredRows() {
+      return this.rows.filter(row => {
+        const accountMatched = !this.query.agent_account || row.agent_account.indexOf(this.query.agent_account) > -1
+        const turnoverMatched = !this.query.turnover_status || row.turnover_status === this.query.turnover_status
+        const profitMatched = !this.query.profit_status || row.profit_status === this.query.profit_status
+        return accountMatched && turnoverMatched && profitMatched
+      })
+    },
+    pagedRows() {
+      const start = (this.page - 1) * this.limit
+      return this.filteredRows.slice(start, start + this.limit)
+    }
+  },
+  methods: {
+    handleSearch() {
+      this.page = 1
+      this.selectedRows = []
+    },
+    handleSelectionChange(rows) {
+      this.selectedRows = rows
+    },
+    handleRebate(row) {
+      this.$confirm('确定对该代理进行流水返佣吗?', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        row.turnover_status = 'settled'
+        this.$message.success('流水返佣成功')
+        this.selectedRows = []
+        this.$refs.rebateTable.clearSelection()
+      }).catch(() => {})
+    },
+    handleBatchRebate() {
+      if (!this.selectedRows.length) return
+      this.$confirm(`确定对选中的 ${this.selectedRows.length} 条记录进行流水返佣吗?`, '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        this.selectedRows.forEach(row => { row.turnover_status = 'settled' })
+        this.$message.success('批量流水返佣成功')
+        this.selectedRows = []
+        this.$refs.rebateTable.clearSelection()
+      }).catch(() => {})
+    },
+    handleSizeChange(value) {
+      this.limit = value
+      this.page = 1
+    },
+    handleCurrentChange(value) {
+      this.page = value
+    },
+    formatMoney(value) {
+      return Number(value || 0).toFixed(2)
+    },
+    formatRate(value) {
+      return `${Number(value || 0).toFixed(2).replace(/\.00$/, '')}%`
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.agent-rebate-page {
+  width: 100%;
+  height: 100%;
+  padding: 16px;
+  overflow: hidden;
+  background: #f3f6f8;
+}
+
+.rebate-panel {
+  display: flex;
+  flex-direction: column;
+  height: 100%;
+  padding: 20px 20px 12px;
+  overflow: hidden;
+  background: #fff;
+  box-shadow: 0 1px 8px rgba(0, 0, 0, 0.08);
+}
+
+.rebate-search {
+  flex-shrink: 0;
+  padding-bottom: 8px;
+
+  ::v-deep .el-form-item {
+    margin-right: 14px;
+    margin-bottom: 14px;
+  }
+
+  ::v-deep .el-form-item__label {
+    color: #536273;
+    font-weight: 500;
+  }
+
+  ::v-deep .el-input,
+  ::v-deep .el-select {
+    width: 236px;
+  }
+
+  .date-range {
+    width: 400px;
+  }
+}
+
+.search-actions {
+  margin-right: 0 !important;
+
+  ::v-deep .el-button {
+    min-width: 70px;
+    margin-left: 0;
+    margin-right: 14px;
+  }
+
+  ::v-deep .el-button--danger.is-disabled {
+    color: #fff;
+    background: #ffb3b5;
+    border-color: #ffb3b5;
+  }
+}
+
+.rebate-table {
+  flex: 1;
+  min-height: 0;
+
+  ::v-deep th {
+    height: 44px;
+    color: #7b8794;
+    font-weight: 600;
+    background: #fff;
+  }
+
+  ::v-deep td {
+    height: 45px;
+    color: #334155;
+  }
+}
+
+.table-help {
+  margin-left: 3px;
+  color: #7b8794;
+  cursor: help;
+}
+
+.rebate-pagination {
+  flex-shrink: 0;
+  padding-top: 12px;
+  text-align: right;
+}
+
+.empty-action {
+  color: #909399;
+}
+
+@media (max-width: 1280px) {
+  .rebate-search {
+    ::v-deep .el-input,
+    ::v-deep .el-select {
+      width: 190px;
+    }
+
+    .date-range {
+      width: 330px;
+    }
+  }
+}
+</style>

+ 346 - 0
src/views/agent/turnoverCommission/index.vue

@@ -0,0 +1,346 @@
+<template>
+  <div class="agent-turnover-commission-page">
+    <div class="commission-panel">
+      <div class="panel-toolbar">
+        <el-button type="primary" icon="el-icon-plus" size="small" @click="handleAdd">新增</el-button>
+      </div>
+
+      <el-table v-loading="loading" :data="pagedRows" border class="commission-table" height="calc(100vh - 225px)">
+        <el-table-column type="index" label="序号" width="70" align="center" />
+        <el-table-column prop="name" label="流水佣金比例名称" min-width="170" align="center" />
+        <el-table-column prop="casino" label="真人流水佣金比例" min-width="145" align="center">
+          <template slot-scope="{ row }">{{ formatRate(row.casino) }}</template>
+        </el-table-column>
+        <el-table-column prop="tiger" label="老虎机流水佣金比例" min-width="145" align="center">
+          <template slot-scope="{ row }">{{ formatRate(row.tiger) }}</template>
+        </el-table-column>
+        <el-table-column prop="lottery" label="彩票流水佣金比例" min-width="145" align="center">
+          <template slot-scope="{ row }">{{ formatRate(row.lottery) }}</template>
+        </el-table-column>
+        <el-table-column prop="sports" label="体育流水佣金比例" min-width="145" align="center">
+          <template slot-scope="{ row }">{{ formatRate(row.sports) }}</template>
+        </el-table-column>
+        <el-table-column prop="esports" label="电竞流水佣金比例" min-width="145" align="center">
+          <template slot-scope="{ row }">{{ formatRate(row.esports) }}</template>
+        </el-table-column>
+        <el-table-column prop="fishing" label="捕鱼流水佣金比例" min-width="145" align="center">
+          <template slot-scope="{ row }">{{ formatRate(row.fishing) }}</template>
+        </el-table-column>
+        <el-table-column prop="chess" label="棋牌流水佣金比例" min-width="145" align="center">
+          <template slot-scope="{ row }">{{ formatRate(row.chess) }}</template>
+        </el-table-column>
+        <el-table-column label="是否默认" width="100" align="center">
+          <template slot-scope="{ row }">
+            <el-tag v-if="row.is_default" type="success" size="mini" effect="plain">是</el-tag>
+            <span v-else class="muted">否</span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="created_at" label="创建时间" width="170" align="center" />
+        <el-table-column label="操作" width="120" fixed="right" align="center">
+          <template slot-scope="{ row }">
+            <el-button type="text" size="mini" @click="handleEdit(row)">修改</el-button>
+            <el-button type="text" size="mini" class="danger-action" @click="handleDelete(row)">删除</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <el-pagination
+        background
+        class="commission-pagination"
+        layout="total, sizes, prev, pager, next, jumper"
+        :current-page="page"
+        :page-size="limit"
+        :page-sizes="[10, 20, 50, 100]"
+        :total="rows.length"
+        @size-change="handleSizeChange"
+        @current-change="handleCurrentChange"
+      />
+    </div>
+
+    <el-dialog
+      :title="dialogType === 'add' ? '新增' : '修改'"
+      :visible.sync="dialogVisible"
+      width="50%"
+      top="15vh"
+      @close="resetForm"
+    >
+      <el-form ref="commissionForm" :model="form" :rules="rules" label-width="120px" size="small" class="commission-form">
+        <el-form-item label="返佣比例名称" prop="name">
+          <el-input v-model="form.name" maxlength="30" placeholder="返佣比例名称" />
+        </el-form-item>
+
+        <el-form-item v-for="field in rateFields" :key="field.prop" :label="field.label" :prop="field.prop">
+          <div class="rate-control">
+            <el-input v-model="form[field.prop]" :placeholder="field.placeholder" />
+            <span class="unit">%</span>
+          </div>
+        </el-form-item>
+
+        <div class="default-tip">
+          <span>注意:</span>
+          <strong>默认比例仅存在一条,如果勾选默认会覆盖掉已配置过此比例的代理</strong>
+        </div>
+
+        <el-form-item label="是否默认" prop="is_default" class="default-form-item">
+          <el-radio-group v-model="form.is_default">
+            <el-radio :label="false">否</el-radio>
+            <el-radio :label="true">是</el-radio>
+          </el-radio-group>
+        </el-form-item>
+      </el-form>
+      <div slot="footer">
+        <el-button size="small" @click="dialogVisible = false">取消</el-button>
+        <el-button type="primary" size="small" @click="submitForm">确定</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+const emptyForm = () => ({
+  id: null,
+  name: '',
+  casino: '',
+  tiger: '',
+  lottery: '',
+  sports: '',
+  esports: '',
+  fishing: '',
+  chess: '',
+  is_default: false
+})
+
+export default {
+  name: 'AgentTurnoverCommission',
+  data() {
+    return {
+      loading: false,
+      page: 1,
+      limit: 10,
+      dialogVisible: false,
+      dialogType: 'add',
+      form: emptyForm(),
+      rows: [
+        {
+          id: 1,
+          name: '流水-默认比例',
+          casino: 0.8,
+          tiger: 2,
+          lottery: 0.5,
+          sports: 0.5,
+          esports: 0.5,
+          fishing: 0.5,
+          chess: 0.5,
+          is_default: true,
+          created_at: '2024-11-14 09:02:33'
+        }
+      ],
+      rateFields: [
+        { prop: 'casino', label: '真人返佣比例', placeholder: '真人返佣比例' },
+        { prop: 'tiger', label: '老虎机返佣比例', placeholder: '老虎机返佣比例' },
+        { prop: 'lottery', label: '彩票返佣比例', placeholder: '彩票返佣比例' },
+        { prop: 'sports', label: '体育返佣比例', placeholder: '体育返佣比例' },
+        { prop: 'esports', label: '电竞返佣比例', placeholder: '电竞返佣比例' },
+        { prop: 'fishing', label: '捕鱼返佣比例', placeholder: '捕鱼返佣比例' },
+        { prop: 'chess', label: '棋牌返佣比例', placeholder: '棋牌返佣比例' }
+      ],
+      rules: {
+        name: [{ required: true, message: '请输入比例名称', trigger: 'blur' }],
+        casino: [{ required: true, message: '请输入真人返佣比例', trigger: 'blur' }, { validator: this.validateRate, trigger: 'blur' }],
+        tiger: [{ required: true, message: '请输入老虎机返佣比例', trigger: 'blur' }, { validator: this.validateRate, trigger: 'blur' }],
+        lottery: [{ required: true, message: '请输入彩票返佣比例', trigger: 'blur' }, { validator: this.validateRate, trigger: 'blur' }],
+        sports: [{ required: true, message: '请输入体育返佣比例', trigger: 'blur' }, { validator: this.validateRate, trigger: 'blur' }],
+        esports: [{ required: true, message: '请输入电竞返佣比例', trigger: 'blur' }, { validator: this.validateRate, trigger: 'blur' }],
+        fishing: [{ required: true, message: '请输入捕鱼返佣比例', trigger: 'blur' }, { validator: this.validateRate, trigger: 'blur' }],
+        chess: [{ required: true, message: '请输入棋牌返佣比例', trigger: 'blur' }, { validator: this.validateRate, trigger: 'blur' }]
+      }
+    }
+  },
+  computed: {
+    pagedRows() {
+      const start = (this.page - 1) * this.limit
+      return this.rows.slice(start, start + this.limit)
+    }
+  },
+  methods: {
+    validateRate(rule, value, callback) {
+      const rate = Number(value)
+      if (value === '' || Number.isNaN(rate) || rate < 0 || rate > 100) {
+        callback(new Error('请输入0-100之间的比例'))
+        return
+      }
+      callback()
+    },
+    formatRate(value) {
+      return `${Number(value || 0).toFixed(2).replace(/\.00$/, '')}%`
+    },
+    handleAdd() {
+      this.dialogType = 'add'
+      this.form = emptyForm()
+      this.dialogVisible = true
+    },
+    handleEdit(row) {
+      this.dialogType = 'edit'
+      this.form = { ...row }
+      this.dialogVisible = true
+    },
+    handleDelete(row) {
+      if (row.is_default) {
+        this.$message.warning('默认比例不能删除')
+        return
+      }
+      this.$confirm('确定删除该流水佣金比例吗?', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        this.rows = this.rows.filter(item => item.id !== row.id)
+        this.$message.success('删除成功')
+        if (this.page > 1 && this.pagedRows.length === 0) this.page -= 1
+      }).catch(() => {})
+    },
+    submitForm() {
+      this.$refs.commissionForm.validate(valid => {
+        if (!valid) return
+        const data = { ...this.form }
+        Object.keys(data).forEach(key => {
+          if (key !== 'name' && key !== 'id' && key !== 'created_at' && key !== 'is_default') {
+            data[key] = data[key] === '' ? 0 : Number(data[key])
+          }
+        })
+        if (data.is_default) this.rows.forEach(item => { item.is_default = false })
+        if (this.dialogType === 'add') {
+          data.id = Date.now()
+          data.created_at = this.formatDate(new Date())
+          this.rows.push(data)
+        } else {
+          const index = this.rows.findIndex(item => item.id === data.id)
+          if (index > -1) this.$set(this.rows, index, data)
+        }
+        this.dialogVisible = false
+        this.$message.success(this.dialogType === 'add' ? '新增成功' : '修改成功')
+      })
+    },
+    resetForm() {
+      if (this.$refs.commissionForm) this.$refs.commissionForm.resetFields()
+      this.form = emptyForm()
+    },
+    formatDate(date) {
+      const pad = value => String(value).padStart(2, '0')
+      return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`
+    },
+    handleSizeChange(value) {
+      this.limit = value
+      this.page = 1
+    },
+    handleCurrentChange(value) {
+      this.page = value
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.agent-turnover-commission-page {
+  width: 100%;
+  height: 100%;
+  padding: 16px;
+  overflow: hidden;
+  background: #f3f6f8;
+}
+
+.commission-panel {
+  height: 100%;
+  padding: 20px 20px 12px;
+  overflow: hidden;
+  background: #fff;
+  box-shadow: 0 1px 8px rgba(0, 0, 0, 0.08);
+}
+
+.panel-toolbar {
+  height: 44px;
+}
+
+.commission-table {
+  width: 100%;
+
+  ::v-deep th {
+    height: 44px;
+    color: #7b8794;
+    font-weight: 600;
+    background: #fff;
+  }
+
+  ::v-deep td {
+    height: 45px;
+    color: #334155;
+  }
+}
+
+.commission-pagination {
+  padding-top: 12px;
+  text-align: right;
+}
+
+.commission-form {
+  padding: 4px 16px 0;
+
+  ::v-deep .el-form-item {
+    margin-bottom: 24px;
+  }
+
+  ::v-deep .el-form-item__label {
+    color: #606266;
+  }
+
+  ::v-deep .el-input__inner {
+    height: 36px;
+    line-height: 36px;
+  }
+}
+
+.rate-control {
+  display: flex;
+  align-items: stretch;
+
+  .el-input {
+    flex: 1;
+  }
+}
+
+.unit {
+  display: flex;
+  width: 56px;
+  align-items: center;
+  justify-content: center;
+  color: #909399;
+  background: #f5f7fa;
+  border: 1px solid #dcdfe6;
+  border-left: 0;
+  border-radius: 0 4px 4px 0;
+}
+
+.default-tip {
+  margin: -2px 0 24px 76px;
+  color: #606266;
+  line-height: 24px;
+
+  strong {
+    margin-left: 10px;
+    color: #f5222d;
+    font-size: 16px;
+  }
+}
+
+.default-form-item {
+  margin-bottom: 0 !important;
+}
+
+.muted {
+  color: #909399;
+}
+
+.danger-action {
+  color: #f56c6c;
+}
+</style>

Некоторые файлы не были показаны из-за большого количества измененных файлов