Sfoglia il codice sorgente

coding:投流初版代码

zhangqin 17 ore fa
parent
commit
a54ad69b33

+ 54 - 0
src/api/adv/advertiser.js

@@ -0,0 +1,54 @@
+import request from '@/utils/request'
+
+// 分页查询广告商列表
+export function pageAdvertiser(query) {
+  return request({
+    url: '/advertiser/page',
+    method: 'get',
+    params: query
+  })
+}
+
+// 根据ID查询广告商详情
+export function getAdvertiser(id) {
+  return request({
+    url: '/advertiser/' + id,
+    method: 'get'
+  })
+}
+
+// 创建广告商
+export function addAdvertiser(data) {
+  return request({
+    url: '/advertiser',
+    method: 'post',
+    data: data
+  })
+}
+
+// 更新广告商
+export function updateAdvertiser(id, data) {
+  return request({
+    url: '/advertiser/' + id,
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除广告商
+export function delAdvertiser(id) {
+  return request({
+    url: '/advertiser/' + id,
+    method: 'delete'
+  })
+}
+
+// 批量删除广告商
+export function batchDelAdvertiser(ids) {
+  return request({
+    url: '/advertiser/batch',
+    method: 'delete',
+    data: ids
+  })
+}
+

+ 54 - 0
src/api/adv/callbackAccount.js

@@ -0,0 +1,54 @@
+import request from '@/utils/request'
+
+// 分页查询回传账号列表
+export function pageCallbackAccount(query) {
+  return request({
+    url: '/callback-account/page',
+    method: 'get',
+    params: query
+  })
+}
+
+// 根据ID查询回传账号详情
+export function getCallbackAccount(id) {
+  return request({
+    url: '/callback-account/' + id,
+    method: 'get'
+  })
+}
+
+// 创建回传账号
+export function addCallbackAccount(data) {
+  return request({
+    url: '/callback-account',
+    method: 'post',
+    data: data
+  })
+}
+
+// 更新回传账号
+export function updateCallbackAccount(id, data) {
+  return request({
+    url: '/callback-account/' + id,
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除回传账号
+export function delCallbackAccount(id) {
+  return request({
+    url: '/callback-account/' + id,
+    method: 'delete'
+  })
+}
+
+// 批量删除回传账号
+export function batchDelCallbackAccount(ids) {
+  return request({
+    url: '/callback-account/batch',
+    method: 'delete',
+    data: ids
+  })
+}
+

+ 71 - 0
src/api/adv/domain.js

@@ -0,0 +1,71 @@
+import request from '@/utils/request'
+
+// 分页查询域名列表
+export function pageDomain(query) {
+  return request({
+    url: '/domains/page',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询所有启用的域名
+export function listEnabledDomain() {
+  return request({
+    url: '/domains/list/enabled',
+    method: 'get'
+  })
+}
+
+// 根据ID查询域名详情
+export function getDomain(id) {
+  return request({
+    url: '/domains/' + id,
+    method: 'get'
+  })
+}
+
+// 新增域名
+export function addDomain(data) {
+  return request({
+    url: '/domains',
+    method: 'post',
+    data: data
+  })
+}
+
+// 更新域名
+export function updateDomain(id, data) {
+  return request({
+    url: '/domains/' + id,
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除域名
+export function delDomain(id) {
+  return request({
+    url: '/domains/' + id,
+    method: 'delete'
+  })
+}
+
+// 批量删除域名
+export function batchDelDomain(ids) {
+  return request({
+    url: '/domains/batch',
+    method: 'delete',
+    data: ids
+  })
+}
+
+// 启用/禁用域名
+export function updateDomainStatus(id, status) {
+  return request({
+    url: '/domains/' + id + '/status',
+    method: 'put',
+    params: { status }
+  })
+}
+

+ 79 - 0
src/api/adv/landingPageTemplate.js

@@ -0,0 +1,79 @@
+import request from '@/utils/request'
+
+// 分页查询模板列表
+export function pageTemplate(query) {
+  return request({
+    url: '/landing-page-templates/page',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询所有启用的模板
+export function listEnabledTemplate() {
+  return request({
+    url: '/landing-page-templates/list/enabled',
+    method: 'get'
+  })
+}
+
+// 根据ID查询模板详情
+export function getTemplate(id) {
+  return request({
+    url: '/landing-page-templates/' + id,
+    method: 'get'
+  })
+}
+
+// 新增模板
+export function addTemplate(data) {
+  return request({
+    url: '/landing-page-templates',
+    method: 'post',
+    data: data
+  })
+}
+
+// 更新模板
+export function updateTemplate(id, data) {
+  return request({
+    url: '/landing-page-templates/' + id,
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除模板
+export function delTemplate(id) {
+  return request({
+    url: '/landing-page-templates/' + id,
+    method: 'delete'
+  })
+}
+
+// 批量删除模板
+export function batchDelTemplate(ids) {
+  return request({
+    url: '/landing-page-templates/batch',
+    method: 'delete',
+    data: ids
+  })
+}
+
+// 启用/禁用模板
+export function updateTemplateStatus(id, status) {
+  return request({
+    url: '/landing-page-templates/' + id + '/status',
+    method: 'put',
+    params: { status }
+  })
+}
+
+// 复制模板
+export function copyTemplate(id) {
+  return request({
+    url: '/landing-page-templates/' + id + '/copy',
+    method: 'post'
+  })
+}
+

+ 54 - 0
src/api/adv/leadSource.js

@@ -0,0 +1,54 @@
+import request from '@/utils/request'
+
+// 分页查询线索来源列表
+export function pageLeadSource(query) {
+  return request({
+    url: '/lead-source/page',
+    method: 'get',
+    params: query
+  })
+}
+
+// 根据ID查询线索来源详情
+export function getLeadSource(id) {
+  return request({
+    url: '/lead-source/' + id,
+    method: 'get'
+  })
+}
+
+// 创建线索来源
+export function addLeadSource(data) {
+  return request({
+    url: '/lead-source',
+    method: 'post',
+    data: data
+  })
+}
+
+// 更新线索来源
+export function updateLeadSource(id, data) {
+  return request({
+    url: '/lead-source/' + id,
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除线索来源
+export function delLeadSource(id) {
+  return request({
+    url: '/lead-source/' + id,
+    method: 'delete'
+  })
+}
+
+// 批量删除线索来源
+export function batchDelLeadSource(ids) {
+  return request({
+    url: '/lead-source/batch',
+    method: 'delete',
+    data: ids
+  })
+}
+

+ 54 - 0
src/api/adv/promotionAccount.js

@@ -0,0 +1,54 @@
+import request from '@/utils/request'
+
+// 分页查询推广账号列表
+export function pagePromotionAccount(query) {
+  return request({
+    url: '/promotion-account/page',
+    method: 'get',
+    params: query
+  })
+}
+
+// 根据ID查询推广账号详情
+export function getPromotionAccount(id) {
+  return request({
+    url: '/promotion-account/' + id,
+    method: 'get'
+  })
+}
+
+// 创建推广账号
+export function addPromotionAccount(data) {
+  return request({
+    url: '/promotion-account',
+    method: 'post',
+    data: data
+  })
+}
+
+// 更新推广账号
+export function updatePromotionAccount(id, data) {
+  return request({
+    url: '/promotion-account/' + id,
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除推广账号
+export function delPromotionAccount(id) {
+  return request({
+    url: '/promotion-account/' + id,
+    method: 'delete'
+  })
+}
+
+// 批量删除推广账号
+export function batchDelPromotionAccount(ids) {
+  return request({
+    url: '/promotion-account/batch',
+    method: 'delete',
+    data: ids
+  })
+}
+

+ 52 - 0
src/api/adv/site.js

@@ -0,0 +1,52 @@
+import request from '@/utils/request'
+
+// 查询站点列表
+export function listSite() {
+  return request({
+    url: '/site/list',
+    method: 'get'
+  })
+}
+
+// 查询站点详情
+export function getSite(id) {
+  return request({
+    url: '/site/' + id,
+    method: 'get'
+  })
+}
+
+// 创建站点
+export function addSite(data) {
+  return request({
+    url: '/site',
+    method: 'post',
+    data: data
+  })
+}
+
+// 更新站点
+export function updateSite(id, data) {
+  return request({
+    url: '/site/' + id,
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除站点
+export function delSite(id) {
+  return request({
+    url: '/site/' + id,
+    method: 'delete'
+  })
+}
+
+// 查询站点统计数据
+export function getSiteStatistics(id) {
+  return request({
+    url: '/site/' + id + '/statistics',
+    method: 'get'
+  })
+}
+

+ 36 - 0
src/api/adv/trackingLink.js

@@ -0,0 +1,36 @@
+import request from '@/utils/request'
+
+// 分页查询监测链接列表
+export function pageTrackingLink(query) {
+  return request({
+    url: '/tracking-link/page',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询所有监测链接列表(不分页)
+export function listTrackingLink(query) {
+  return request({
+    url: '/tracking-link/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 根据ID查询监测链接详情
+export function getTrackingLink(id) {
+  return request({
+    url: '/tracking-link/' + id,
+    method: 'get'
+  })
+}
+
+// 根据广告商ID查询监测链接列表
+export function getTrackingLinkByAdvertiser(advertiserId) {
+  return request({
+    url: '/tracking-link/advertiser/' + advertiserId,
+    method: 'get'
+  })
+}
+

+ 1 - 0
src/utils/errorCode.js

@@ -2,5 +2,6 @@ export default {
   '401': '认证失败,无法访问系统资源',
   '403': '当前操作没有权限',
   '404': '访问资源不存在',
+  '500': '服务器内部错误',
   'default': '系统未知错误,请反馈给管理员'
 }

+ 2 - 2
src/utils/request.js

@@ -59,8 +59,8 @@ service.interceptors.request.use(config => {
 service.interceptors.response.use(res => {
     // 未设置状态码则默认成功状态
     const code = res.data.code || 200;
-    // 获取错误信息
-    const msg = errorCode[code] || res.data.msg || errorCode['default']
+    // 获取错误信息 - 优先使用后端返回的 message 或 msg,兼容两种字段名
+    const msg = res.data.message || res.data.msg || errorCode[code] || errorCode['default']
     if (code === 401) {
       MessageBox.confirm('登录状态已过期,您可以继续留在该页面,或者重新登录', '系统提示', {
           confirmButtonText: '重新登录',

+ 279 - 0
src/views/adv/advertiser/index.vue

@@ -0,0 +1,279 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch">
+      <el-form-item label="广告商名称" prop="advertiserName">
+        <el-input
+          v-model="queryParams.advertiserName"
+          placeholder="请输入广告商名称"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="类型" prop="type">
+        <el-select v-model="queryParams.type" placeholder="请选择类型" clearable size="small">
+          <el-option label="类型1" :value="1" />
+          <el-option label="类型2" :value="2" />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="状态" prop="enabled">
+        <el-select v-model="queryParams.enabled" placeholder="请选择状态" clearable size="small">
+          <el-option label="启用" :value="1" />
+          <el-option label="禁用" :value="0" />
+        </el-select>
+      </el-form-item>
+      <el-form-item>
+        <el-button type="cyan" 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-row :gutter="10" class="mb8">
+      <el-col :span="1.5">
+        <el-button
+          plain
+          type="primary"
+          icon="el-icon-plus"
+          size="mini"
+          @click="handleAdd"
+        >新增</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          plain
+          type="danger"
+          icon="el-icon-delete"
+          size="mini"
+          :disabled="multiple"
+          @click="handleDelete"
+        >删除</el-button>
+      </el-col>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+
+    <el-table border v-loading="loading" :data="advertiserList" @selection-change="handleSelectionChange">
+      <el-table-column type="selection" width="55" align="center" />
+      <el-table-column label="ID" align="center" prop="id" width="80" />
+      <el-table-column label="广告商名称" align="center" prop="advertiserName" />
+      <el-table-column label="类型" align="center" prop="type" width="100">
+        <template slot-scope="scope">
+          <span v-if="scope.row.type === 1">类型1</span>
+          <span v-else-if="scope.row.type === 2">类型2</span>
+          <span v-else>{{ scope.row.type }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="状态" align="center" prop="enabled" width="100">
+        <template slot-scope="scope">
+          <el-tag v-if="scope.row.enabled === 1" type="success">启用</el-tag>
+          <el-tag v-else type="danger">禁用</el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="创建时间" align="center" prop="createTime" width="180">
+        <template slot-scope="scope">
+          <span>{{ parseTime(scope.row.createTime) }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="180">
+        <template slot-scope="scope">
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-edit"
+            @click="handleUpdate(scope.row)"
+          >修改</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-delete"
+            @click="handleDelete(scope.row)"
+          >删除</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <pagination
+      v-show="total>0"
+      :total="total"
+      :page.sync="queryParams.current"
+      :limit.sync="queryParams.size"
+      @pagination="getList"
+    />
+
+    <!-- 添加或修改广告商对话框 -->
+    <el-dialog :title="title" :visible.sync="open" width="600px" append-to-body>
+      <el-form ref="form" :model="form" :rules="rules" label-width="100px">
+        <el-form-item label="广告商名称" prop="advertiserName">
+          <el-input v-model="form.advertiserName" placeholder="请输入广告商名称" />
+        </el-form-item>
+        <el-form-item label="类型" prop="type">
+          <el-select v-model="form.type" placeholder="请选择类型" style="width: 100%">
+            <el-option label="类型1" :value="1" />
+            <el-option label="类型2" :value="2" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="状态" prop="enabled">
+          <el-radio-group v-model="form.enabled">
+            <el-radio :label="1">启用</el-radio>
+            <el-radio :label="0">禁用</el-radio>
+          </el-radio-group>
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="submitForm">确 定</el-button>
+        <el-button @click="cancel">取 消</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { pageAdvertiser, getAdvertiser, addAdvertiser, updateAdvertiser, delAdvertiser, batchDelAdvertiser } from "@/api/adv/advertiser";
+
+export default {
+  name: "Advertiser",
+  data() {
+    return {
+      // 遮罩层
+      loading: true,
+      // 选中数组
+      ids: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 广告商表格数据
+      advertiserList: [],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      // 查询参数
+      queryParams: {
+        current: 1,
+        size: 10,
+        advertiserName: undefined,
+        type: undefined,
+        enabled: undefined
+      },
+      // 表单参数
+      form: {},
+      // 表单校验
+      rules: {
+        advertiserName: [
+          { required: true, message: "广告商名称不能为空", trigger: "blur" }
+        ],
+        type: [
+          { required: true, message: "类型不能为空", trigger: "change" }
+        ],
+        enabled: [
+          { required: true, message: "状态不能为空", trigger: "change" }
+        ]
+      }
+    };
+  },
+  created() {
+    this.getList();
+  },
+  methods: {
+    /** 查询广告商列表 */
+    getList() {
+      this.loading = true;
+      pageAdvertiser(this.queryParams).then(response => {
+        this.advertiserList = response.data.records;
+        this.total = response.data.total;
+        this.loading = false;
+      });
+    },
+    // 取消按钮
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        id: undefined,
+        advertiserName: undefined,
+        type: undefined,
+        enabled: 1
+      };
+      this.resetForm("form");
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.current = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm("queryForm");
+      this.handleQuery();
+    },
+    // 多选框选中数据
+    handleSelectionChange(selection) {
+      this.ids = selection.map(item => item.id);
+      this.single = selection.length !== 1;
+      this.multiple = !selection.length;
+    },
+    /** 新增按钮操作 */
+    handleAdd() {
+      this.reset();
+      this.open = true;
+      this.title = "添加广告商";
+    },
+    /** 修改按钮操作 */
+    handleUpdate(row) {
+      this.reset();
+      const id = row.id || this.ids[0];
+      getAdvertiser(id).then(response => {
+        this.form = response.data;
+        this.open = true;
+        this.title = "修改广告商";
+      });
+    },
+    /** 提交按钮 */
+    submitForm() {
+      this.$refs["form"].validate(valid => {
+        if (valid) {
+          if (this.form.id != undefined) {
+            updateAdvertiser(this.form.id, this.form).then(response => {
+              if (response.code === 200) {
+                this.msgSuccess("修改成功");
+                this.open = false;
+                this.getList();
+              }
+            });
+          } else {
+            addAdvertiser(this.form).then(response => {
+              if (response.code === 200) {
+                this.msgSuccess("新增成功");
+                this.open = false;
+                this.getList();
+              }
+            });
+          }
+        }
+      });
+    },
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      const ids = row.id ? [row.id] : this.ids;
+      this.$confirm('是否确认删除选中的广告商?', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(() => {
+        return batchDelAdvertiser(ids);
+      }).then(() => {
+        this.getList();
+        this.msgSuccess("删除成功");
+      }).catch(function() {});
+    }
+  }
+};
+</script>
+

+ 232 - 0
src/views/adv/callbackAccount/index.vue

@@ -0,0 +1,232 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch">
+      <el-form-item label="账号名称" prop="callbackAccountName">
+        <el-input
+          v-model="queryParams.callbackAccountName"
+          placeholder="请输入账号名称"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item>
+        <el-button type="cyan" 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-row :gutter="10" class="mb8">
+      <el-col :span="1.5">
+        <el-button
+          plain
+          type="primary"
+          icon="el-icon-plus"
+          size="mini"
+          @click="handleAdd"
+        >新增</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          plain
+          type="danger"
+          icon="el-icon-delete"
+          size="mini"
+          :disabled="multiple"
+          @click="handleDelete"
+        >删除</el-button>
+      </el-col>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+
+    <el-table border v-loading="loading" :data="accountList" @selection-change="handleSelectionChange">
+      <el-table-column type="selection" width="55" align="center" />
+      <el-table-column label="ID" align="center" prop="id" width="80" />
+      <el-table-column label="账号名称" align="center" prop="callbackAccountName" />
+      <el-table-column label="创建时间" align="center" prop="createTime" width="180">
+        <template slot-scope="scope">
+          <span>{{ parseTime(scope.row.createTime) }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="180">
+        <template slot-scope="scope">
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-edit"
+            @click="handleUpdate(scope.row)"
+          >修改</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-delete"
+            @click="handleDelete(scope.row)"
+          >删除</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <pagination
+      v-show="total>0"
+      :total="total"
+      :page.sync="queryParams.current"
+      :limit.sync="queryParams.size"
+      @pagination="getList"
+    />
+
+    <!-- 添加或修改回传账号对话框 -->
+    <el-dialog :title="title" :visible.sync="open" width="600px" append-to-body>
+      <el-form ref="form" :model="form" :rules="rules" label-width="100px">
+        <el-form-item label="账号名称" prop="callbackAccountName">
+          <el-input v-model="form.callbackAccountName" placeholder="请输入账号名称" />
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="submitForm">确 定</el-button>
+        <el-button @click="cancel">取 消</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { pageCallbackAccount, getCallbackAccount, addCallbackAccount, updateCallbackAccount, delCallbackAccount, batchDelCallbackAccount } from "@/api/adv/callbackAccount";
+
+export default {
+  name: "CallbackAccount",
+  data() {
+    return {
+      // 遮罩层
+      loading: true,
+      // 选中数组
+      ids: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 回传账号表格数据
+      accountList: [],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      // 查询参数
+      queryParams: {
+        current: 1,
+        size: 10,
+        callbackAccountName: undefined
+      },
+      // 表单参数
+      form: {},
+      // 表单校验
+      rules: {
+        callbackAccountName: [
+          { required: true, message: "账号名称不能为空", trigger: "blur" }
+        ]
+      }
+    };
+  },
+  created() {
+    this.getList();
+  },
+  methods: {
+    /** 查询回传账号列表 */
+    getList() {
+      this.loading = true;
+      pageCallbackAccount(this.queryParams).then(response => {
+        this.accountList = response.data.records;
+        this.total = response.data.total;
+        this.loading = false;
+      });
+    },
+    // 取消按钮
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        id: undefined,
+        callbackAccountName: undefined
+      };
+      this.resetForm("form");
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.current = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm("queryForm");
+      this.handleQuery();
+    },
+    // 多选框选中数据
+    handleSelectionChange(selection) {
+      this.ids = selection.map(item => item.id);
+      this.single = selection.length !== 1;
+      this.multiple = !selection.length;
+    },
+    /** 新增按钮操作 */
+    handleAdd() {
+      this.reset();
+      this.open = true;
+      this.title = "添加回传账号";
+    },
+    /** 修改按钮操作 */
+    handleUpdate(row) {
+      this.reset();
+      const id = row.id || this.ids[0];
+      getCallbackAccount(id).then(response => {
+        this.form = response.data;
+        this.open = true;
+        this.title = "修改回传账号";
+      });
+    },
+    /** 提交按钮 */
+    submitForm() {
+      this.$refs["form"].validate(valid => {
+        if (valid) {
+          if (this.form.id != undefined) {
+            updateCallbackAccount(this.form.id, this.form).then(response => {
+              if (response.code === 200) {
+                this.msgSuccess("修改成功");
+                this.open = false;
+                this.getList();
+              }
+            });
+          } else {
+            addCallbackAccount(this.form).then(response => {
+              if (response.code === 200) {
+                this.msgSuccess("新增成功");
+                this.open = false;
+                this.getList();
+              }
+            });
+          }
+        }
+      });
+    },
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      const ids = row.id ? [row.id] : this.ids;
+      this.$confirm('是否确认删除选中的回传账号?', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(() => {
+        return batchDelCallbackAccount(ids);
+      }).then(() => {
+        this.getList();
+        this.msgSuccess("删除成功");
+      }).catch(function() {});
+    }
+  }
+};
+</script>
+

+ 297 - 0
src/views/adv/domain/index.vue

@@ -0,0 +1,297 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch">
+      <el-form-item label="域名名称" prop="name">
+        <el-input
+          v-model="queryParams.name"
+          placeholder="请输入域名名称"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="域名地址" prop="domain">
+        <el-input
+          v-model="queryParams.domain"
+          placeholder="请输入域名地址"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="状态" prop="status">
+        <el-select v-model="queryParams.status" placeholder="请选择状态" clearable size="small">
+          <el-option label="启用" :value="1" />
+          <el-option label="禁用" :value="0" />
+        </el-select>
+      </el-form-item>
+      <el-form-item>
+        <el-button type="cyan" 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-row :gutter="10" class="mb8">
+      <el-col :span="1.5">
+        <el-button
+          plain
+          type="primary"
+          icon="el-icon-plus"
+          size="mini"
+          @click="handleAdd"
+        >新增</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          plain
+          type="danger"
+          icon="el-icon-delete"
+          size="mini"
+          :disabled="multiple"
+          @click="handleDelete"
+        >删除</el-button>
+      </el-col>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+
+    <el-table border v-loading="loading" :data="domainList" @selection-change="handleSelectionChange">
+      <el-table-column type="selection" width="55" align="center" />
+      <el-table-column label="ID" align="center" prop="id" width="80" />
+      <el-table-column label="域名名称" align="center" prop="name" />
+      <el-table-column label="域名地址" align="center" prop="domain" show-overflow-tooltip />
+      <el-table-column label="状态" align="center" prop="status" width="100">
+        <template slot-scope="scope">
+          <el-switch
+            v-model="scope.row.status"
+            :active-value="1"
+            :inactive-value="0"
+            @change="handleStatusChange(scope.row)"
+          ></el-switch>
+        </template>
+      </el-table-column>
+      <el-table-column label="备注" align="center" prop="remark" show-overflow-tooltip />
+      <el-table-column label="创建时间" align="center" prop="createTime" width="180">
+        <template slot-scope="scope">
+          <span>{{ parseTime(scope.row.createTime) }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="180">
+        <template slot-scope="scope">
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-edit"
+            @click="handleUpdate(scope.row)"
+          >修改</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-delete"
+            @click="handleDelete(scope.row)"
+          >删除</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <pagination
+      v-show="total>0"
+      :total="total"
+      :page.sync="queryParams.page"
+      :limit.sync="queryParams.size"
+      @pagination="getList"
+    />
+
+    <!-- 添加或修改域名对话框 -->
+    <el-dialog :title="title" :visible.sync="open" width="600px" append-to-body>
+      <el-form ref="form" :model="form" :rules="rules" label-width="100px">
+        <el-form-item label="域名名称" prop="name">
+          <el-input v-model="form.name" placeholder="请输入域名名称" />
+        </el-form-item>
+        <el-form-item label="域名地址" prop="domain">
+          <el-input v-model="form.domain" placeholder="请输入域名地址,如:www.example.com" />
+        </el-form-item>
+        <el-form-item label="状态" prop="status">
+          <el-radio-group v-model="form.status">
+            <el-radio :label="1">启用</el-radio>
+            <el-radio :label="0">禁用</el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item label="备注" prop="remark">
+          <el-input v-model="form.remark" type="textarea" placeholder="请输入备注" />
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="submitForm">确 定</el-button>
+        <el-button @click="cancel">取 消</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { pageDomain, getDomain, addDomain, updateDomain, delDomain, batchDelDomain, updateDomainStatus } from "@/api/adv/domain";
+
+export default {
+  name: "Domain",
+  data() {
+    return {
+      // 遮罩层
+      loading: true,
+      // 选中数组
+      ids: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 域名表格数据
+      domainList: [],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      // 查询参数
+      queryParams: {
+        page: 1,
+        size: 10,
+        name: undefined,
+        domain: undefined,
+        status: undefined
+      },
+      // 表单参数
+      form: {},
+      // 表单校验
+      rules: {
+        name: [
+          { required: true, message: "域名名称不能为空", trigger: "blur" }
+        ],
+        domain: [
+          { required: true, message: "域名地址不能为空", trigger: "blur" }
+        ],
+        status: [
+          { required: true, message: "状态不能为空", trigger: "change" }
+        ]
+      }
+    };
+  },
+  created() {
+    this.getList();
+  },
+  methods: {
+    /** 查询域名列表 */
+    getList() {
+      this.loading = true;
+      pageDomain(this.queryParams).then(response => {
+        this.domainList = response.data.records;
+        this.total = response.data.total;
+        this.loading = false;
+      });
+    },
+    // 域名状态修改
+    handleStatusChange(row) {
+      let text = row.status === 1 ? "启用" : "禁用";
+      this.$confirm('确认要"' + text + '""' + row.name + '"域名吗?', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(() => {
+        return updateDomainStatus(row.id, row.status);
+      }).then(() => {
+        this.msgSuccess(text + "成功");
+      }).catch(() => {
+        row.status = row.status === 0 ? 1 : 0;
+      });
+    },
+    // 取消按钮
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        id: undefined,
+        name: undefined,
+        domain: undefined,
+        status: 1,
+        remark: undefined
+      };
+      this.resetForm("form");
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.page = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm("queryForm");
+      this.handleQuery();
+    },
+    // 多选框选中数据
+    handleSelectionChange(selection) {
+      this.ids = selection.map(item => item.id);
+      this.single = selection.length !== 1;
+      this.multiple = !selection.length;
+    },
+    /** 新增按钮操作 */
+    handleAdd() {
+      this.reset();
+      this.open = true;
+      this.title = "添加域名";
+    },
+    /** 修改按钮操作 */
+    handleUpdate(row) {
+      this.reset();
+      const id = row.id || this.ids[0];
+      getDomain(id).then(response => {
+        this.form = response.data;
+        this.open = true;
+        this.title = "修改域名";
+      });
+    },
+    /** 提交按钮 */
+    submitForm() {
+      this.$refs["form"].validate(valid => {
+        if (valid) {
+          if (this.form.id != undefined) {
+            updateDomain(this.form.id, this.form).then(response => {
+              if (response.code === 200) {
+                this.msgSuccess("修改成功");
+                this.open = false;
+                this.getList();
+              }
+            });
+          } else {
+            addDomain(this.form).then(response => {
+              if (response.code === 200) {
+                this.msgSuccess("新增成功");
+                this.open = false;
+                this.getList();
+              }
+            });
+          }
+        }
+      });
+    },
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      const ids = row.id ? [row.id] : this.ids;
+      this.$confirm('是否确认删除选中的域名?', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(() => {
+        return batchDelDomain(ids);
+      }).then(() => {
+        this.getList();
+        this.msgSuccess("删除成功");
+      }).catch(function() {});
+    }
+  }
+};
+</script>
+

+ 374 - 0
src/views/adv/landingPageTemplate/index.vue

@@ -0,0 +1,374 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch">
+      <el-form-item label="模板名称" prop="templateName">
+        <el-input
+          v-model="queryParams.templateName"
+          placeholder="请输入模板名称"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="模板类型" prop="templateType">
+        <el-input
+          v-model="queryParams.templateType"
+          placeholder="请输入模板类型"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="域名" prop="domainId">
+        <el-select v-model="queryParams.domainId" placeholder="请选择域名" clearable size="small">
+          <el-option
+            v-for="item in domainOptions"
+            :key="item.id"
+            :label="item.name"
+            :value="item.id"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="状态" prop="status">
+        <el-select v-model="queryParams.status" placeholder="请选择状态" clearable size="small">
+          <el-option label="启用" :value="1" />
+          <el-option label="禁用" :value="0" />
+        </el-select>
+      </el-form-item>
+      <el-form-item>
+        <el-button type="cyan" 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-row :gutter="10" class="mb8">
+      <el-col :span="1.5">
+        <el-button
+          plain
+          type="primary"
+          icon="el-icon-plus"
+          size="mini"
+          @click="handleAdd"
+        >新增</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          plain
+          type="danger"
+          icon="el-icon-delete"
+          size="mini"
+          :disabled="multiple"
+          @click="handleDelete"
+        >删除</el-button>
+      </el-col>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+
+    <el-table border v-loading="loading" :data="templateList" @selection-change="handleSelectionChange">
+      <el-table-column type="selection" width="55" align="center" />
+      <el-table-column label="ID" align="center" prop="id" width="200" show-overflow-tooltip />
+      <el-table-column label="模板名称" align="center" prop="templateName" />
+      <el-table-column label="模板类型" align="center" prop="templateType" width="120" />
+      <el-table-column label="域名" align="center" prop="domainId" width="150">
+        <template slot-scope="scope">
+          <span>{{ getDomainName(scope.row.domainId) }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="状态" align="center" prop="status" width="100">
+        <template slot-scope="scope">
+          <el-switch
+            v-model="scope.row.status"
+            :active-value="1"
+            :inactive-value="0"
+            @change="handleStatusChange(scope.row)"
+          ></el-switch>
+        </template>
+      </el-table-column>
+      <el-table-column label="备注" align="center" prop="remark" show-overflow-tooltip />
+      <el-table-column label="创建时间" align="center" prop="createTime" width="180">
+        <template slot-scope="scope">
+          <span>{{ parseTime(scope.row.createTime) }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="210">
+        <template slot-scope="scope">
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-edit"
+            @click="handleUpdate(scope.row)"
+          >修改</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-document-copy"
+            @click="handleCopy(scope.row)"
+          >复制</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-delete"
+            @click="handleDelete(scope.row)"
+          >删除</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <pagination
+      v-show="total>0"
+      :total="total"
+      :page.sync="queryParams.page"
+      :limit.sync="queryParams.size"
+      @pagination="getList"
+    />
+
+    <!-- 添加或修改模板对话框 -->
+    <el-dialog :title="title" :visible.sync="open" width="800px" append-to-body>
+      <el-form ref="form" :model="form" :rules="rules" label-width="100px">
+        <el-form-item label="模板名称" prop="templateName">
+          <el-input v-model="form.templateName" placeholder="请输入模板名称" />
+        </el-form-item>
+        <el-form-item label="模板类型" prop="templateType">
+          <el-input v-model="form.templateType" placeholder="请输入模板类型,如:DEFAULT" />
+        </el-form-item>
+        <el-form-item label="关联域名" prop="domainId">
+          <el-select v-model="form.domainId" placeholder="请选择域名" style="width: 100%">
+            <el-option
+              v-for="item in domainOptions"
+              :key="item.id"
+              :label="item.name"
+              :value="item.id"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="模板数据" prop="templateData">
+          <el-input 
+            v-model="form.templateData" 
+            type="textarea" 
+            :rows="10"
+            placeholder="请输入模板数据(JSON格式)" 
+          />
+        </el-form-item>
+        <el-form-item label="状态" prop="status">
+          <el-radio-group v-model="form.status">
+            <el-radio :label="1">启用</el-radio>
+            <el-radio :label="0">禁用</el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item label="备注" prop="remark">
+          <el-input v-model="form.remark" type="textarea" placeholder="请输入备注" />
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="submitForm">确 定</el-button>
+        <el-button @click="cancel">取 消</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { pageTemplate, getTemplate, addTemplate, updateTemplate, delTemplate, batchDelTemplate, updateTemplateStatus, copyTemplate } from "@/api/adv/landingPageTemplate";
+import { listEnabledDomain } from "@/api/adv/domain";
+
+export default {
+  name: "LandingPageTemplate",
+  data() {
+    return {
+      // 遮罩层
+      loading: true,
+      // 选中数组
+      ids: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 模板表格数据
+      templateList: [],
+      // 域名选项
+      domainOptions: [],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      // 查询参数
+      queryParams: {
+        page: 1,
+        size: 10,
+        templateName: undefined,
+        templateType: undefined,
+        domainId: undefined,
+        status: undefined
+      },
+      // 表单参数
+      form: {},
+      // 表单校验
+      rules: {
+        templateName: [
+          { required: true, message: "模板名称不能为空", trigger: "blur" }
+        ],
+        status: [
+          { required: true, message: "状态不能为空", trigger: "change" }
+        ]
+      }
+    };
+  },
+  created() {
+    this.getList();
+    this.getDomainOptions();
+  },
+  methods: {
+    /** 查询域名选项 */
+    getDomainOptions() {
+      listEnabledDomain().then(response => {
+        this.domainOptions = response.data;
+      });
+    },
+    /** 获取域名名称 */
+    getDomainName(domainId) {
+      const domain = this.domainOptions.find(item => item.id === domainId);
+      return domain ? domain.name : domainId;
+    },
+    /** 查询模板列表 */
+    getList() {
+      this.loading = true;
+      pageTemplate(this.queryParams).then(response => {
+        this.templateList = response.data.records;
+        this.total = response.data.total;
+        this.loading = false;
+      });
+    },
+    // 模板状态修改
+    handleStatusChange(row) {
+      let text = row.status === 1 ? "启用" : "禁用";
+      this.$confirm('确认要"' + text + '""' + row.templateName + '"模板吗?', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(() => {
+        return updateTemplateStatus(row.id, row.status);
+      }).then(() => {
+        this.msgSuccess(text + "成功");
+      }).catch(() => {
+        row.status = row.status === 0 ? 1 : 0;
+      });
+    },
+    // 取消按钮
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        id: undefined,
+        templateName: undefined,
+        templateData: undefined,
+        templateType: "DEFAULT",
+        domainId: undefined,
+        status: 1,
+        remark: undefined
+      };
+      this.resetForm("form");
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.page = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm("queryForm");
+      this.handleQuery();
+    },
+    // 多选框选中数据
+    handleSelectionChange(selection) {
+      this.ids = selection.map(item => item.id);
+      this.single = selection.length !== 1;
+      this.multiple = !selection.length;
+    },
+    /** 新增按钮操作 */
+    handleAdd() {
+      this.reset();
+      this.open = true;
+      this.title = "添加模板";
+    },
+    /** 修改按钮操作 */
+    handleUpdate(row) {
+      this.reset();
+      const id = row.id || this.ids[0];
+      getTemplate(id).then(response => {
+        this.form = response.data;
+        this.open = true;
+        this.title = "修改模板";
+      });
+    },
+    /** 复制按钮操作 */
+    handleCopy(row) {
+      this.$confirm('是否确认复制"' + row.templateName + '"模板?', "提示", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "info"
+      }).then(() => {
+        return copyTemplate(row.id);
+      }).then(() => {
+        this.getList();
+        this.msgSuccess("复制成功");
+      }).catch(function() {});
+    },
+    /** 提交按钮 */
+    submitForm() {
+      this.$refs["form"].validate(valid => {
+        if (valid) {
+          // 验证JSON格式
+          if (this.form.templateData) {
+            try {
+              JSON.parse(this.form.templateData);
+            } catch (e) {
+              this.msgError("模板数据格式不正确,必须是有效的JSON");
+              return;
+            }
+          }
+          
+          if (this.form.id != undefined) {
+            updateTemplate(this.form.id, this.form).then(response => {
+              if (response.code === 200) {
+                this.msgSuccess("修改成功");
+                this.open = false;
+                this.getList();
+              }
+            });
+          } else {
+            addTemplate(this.form).then(response => {
+              if (response.code === 200) {
+                this.msgSuccess("新增成功");
+                this.open = false;
+                this.getList();
+              }
+            });
+          }
+        }
+      });
+    },
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      const ids = row.id ? [row.id] : this.ids;
+      this.$confirm('是否确认删除选中的模板?', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(() => {
+        return batchDelTemplate(ids);
+      }).then(() => {
+        this.getList();
+        this.msgSuccess("删除成功");
+      }).catch(function() {});
+    }
+  }
+};
+</script>
+

+ 256 - 0
src/views/adv/leadSource/index.vue

@@ -0,0 +1,256 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch">
+      <el-form-item label="来源名称" prop="sourceName">
+        <el-input
+          v-model="queryParams.sourceName"
+          placeholder="请输入来源名称"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="分配类型" prop="distributeType">
+        <el-select v-model="queryParams.distributeType" placeholder="请选择分配类型" clearable size="small">
+          <el-option label="类型1" :value="1" />
+          <el-option label="类型2" :value="2" />
+        </el-select>
+      </el-form-item>
+      <el-form-item>
+        <el-button type="cyan" 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-row :gutter="10" class="mb8">
+      <el-col :span="1.5">
+        <el-button
+          plain
+          type="primary"
+          icon="el-icon-plus"
+          size="mini"
+          @click="handleAdd"
+        >新增</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          plain
+          type="danger"
+          icon="el-icon-delete"
+          size="mini"
+          :disabled="multiple"
+          @click="handleDelete"
+        >删除</el-button>
+      </el-col>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+
+    <el-table border v-loading="loading" :data="leadSourceList" @selection-change="handleSelectionChange">
+      <el-table-column type="selection" width="55" align="center" />
+      <el-table-column label="ID" align="center" prop="id" width="80" />
+      <el-table-column label="来源名称" align="center" prop="sourceName" />
+      <el-table-column label="分配类型" align="center" prop="distributeType" width="120">
+        <template slot-scope="scope">
+          <span v-if="scope.row.distributeType === 1">类型1</span>
+          <span v-else-if="scope.row.distributeType === 2">类型2</span>
+          <span v-else>{{ scope.row.distributeType }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="创建时间" align="center" prop="createTime" width="180">
+        <template slot-scope="scope">
+          <span>{{ parseTime(scope.row.createTime) }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="180">
+        <template slot-scope="scope">
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-edit"
+            @click="handleUpdate(scope.row)"
+          >修改</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-delete"
+            @click="handleDelete(scope.row)"
+          >删除</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <pagination
+      v-show="total>0"
+      :total="total"
+      :page.sync="queryParams.current"
+      :limit.sync="queryParams.size"
+      @pagination="getList"
+    />
+
+    <!-- 添加或修改线索来源对话框 -->
+    <el-dialog :title="title" :visible.sync="open" width="600px" append-to-body>
+      <el-form ref="form" :model="form" :rules="rules" label-width="100px">
+        <el-form-item label="来源名称" prop="sourceName">
+          <el-input v-model="form.sourceName" placeholder="请输入来源名称" />
+        </el-form-item>
+        <el-form-item label="分配类型" prop="distributeType">
+          <el-select v-model="form.distributeType" placeholder="请选择分配类型" style="width: 100%">
+            <el-option label="类型1" :value="1" />
+            <el-option label="类型2" :value="2" />
+          </el-select>
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="submitForm">确 定</el-button>
+        <el-button @click="cancel">取 消</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { pageLeadSource, getLeadSource, addLeadSource, updateLeadSource, delLeadSource, batchDelLeadSource } from "@/api/adv/leadSource";
+
+export default {
+  name: "LeadSource",
+  data() {
+    return {
+      // 遮罩层
+      loading: true,
+      // 选中数组
+      ids: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 线索来源表格数据
+      leadSourceList: [],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      // 查询参数
+      queryParams: {
+        current: 1,
+        size: 10,
+        sourceName: undefined,
+        distributeType: undefined
+      },
+      // 表单参数
+      form: {},
+      // 表单校验
+      rules: {
+        sourceName: [
+          { required: true, message: "来源名称不能为空", trigger: "blur" }
+        ],
+        distributeType: [
+          { required: true, message: "分配类型不能为空", trigger: "change" }
+        ]
+      }
+    };
+  },
+  created() {
+    this.getList();
+  },
+  methods: {
+    /** 查询线索来源列表 */
+    getList() {
+      this.loading = true;
+      pageLeadSource(this.queryParams).then(response => {
+        this.leadSourceList = response.data.records;
+        this.total = response.data.total;
+        this.loading = false;
+      });
+    },
+    // 取消按钮
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        id: undefined,
+        sourceName: undefined,
+        distributeType: undefined
+      };
+      this.resetForm("form");
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.current = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm("queryForm");
+      this.handleQuery();
+    },
+    // 多选框选中数据
+    handleSelectionChange(selection) {
+      this.ids = selection.map(item => item.id);
+      this.single = selection.length !== 1;
+      this.multiple = !selection.length;
+    },
+    /** 新增按钮操作 */
+    handleAdd() {
+      this.reset();
+      this.open = true;
+      this.title = "添加线索来源";
+    },
+    /** 修改按钮操作 */
+    handleUpdate(row) {
+      this.reset();
+      const id = row.id || this.ids[0];
+      getLeadSource(id).then(response => {
+        this.form = response.data;
+        this.open = true;
+        this.title = "修改线索来源";
+      });
+    },
+    /** 提交按钮 */
+    submitForm() {
+      this.$refs["form"].validate(valid => {
+        if (valid) {
+          if (this.form.id != undefined) {
+            updateLeadSource(this.form.id, this.form).then(response => {
+              if (response.code === 200) {
+                this.msgSuccess("修改成功");
+                this.open = false;
+                this.getList();
+              }
+            });
+          } else {
+            addLeadSource(this.form).then(response => {
+              if (response.code === 200) {
+                this.msgSuccess("新增成功");
+                this.open = false;
+                this.getList();
+              }
+            });
+          }
+        }
+      });
+    },
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      const ids = row.id ? [row.id] : this.ids;
+      this.$confirm('是否确认删除选中的线索来源?', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(() => {
+        return batchDelLeadSource(ids);
+      }).then(() => {
+        this.getList();
+        this.msgSuccess("删除成功");
+      }).catch(function() {});
+    }
+  }
+};
+</script>
+

+ 656 - 0
src/views/adv/promotionAccount/index.vue

@@ -0,0 +1,656 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch">
+      <el-form-item label="账号名称" prop="accountName">
+        <el-input
+          v-model="queryParams.accountName"
+          placeholder="请输入账号名称"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="广告商" prop="advertiserId">
+        <el-select v-model="queryParams.advertiserId" placeholder="请选择广告商" clearable size="small" filterable>
+          <el-option
+            v-for="item in advertiserOptions"
+            :key="item.id"
+            :label="item.advertiserName"
+            :value="item.id"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item>
+        <el-button type="cyan" 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-row :gutter="10" class="mb8">
+      <el-col :span="1.5">
+        <el-button
+          plain
+          type="primary"
+          icon="el-icon-plus"
+          size="mini"
+          @click="handleAdd"
+        >新增</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          plain
+          type="danger"
+          icon="el-icon-delete"
+          size="mini"
+          :disabled="multiple"
+          @click="handleDelete"
+        >删除</el-button>
+      </el-col>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+
+    <el-table border v-loading="loading" :data="accountList" @selection-change="handleSelectionChange">
+      <el-table-column type="selection" width="55" align="center" />
+      <el-table-column label="ID" align="center" prop="id" width="80" />
+      <el-table-column label="账户简称" align="center" prop="accountShortName" width="150" show-overflow-tooltip />
+      <el-table-column label="广告商名称" align="center" prop="advertiserName" width="150" show-overflow-tooltip />
+      <el-table-column label="读取方式" align="center" prop="apiSwitch" width="120">
+        <template slot-scope="scope">
+          <el-tag :type="scope.row.apiSwitch === 1 ? 'success' : 'info'" size="small">
+            {{ scope.row.apiSwitch === 1 ? 'API读取' : '手动录入' }}
+          </el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="广告主ID" align="center" prop="adAccountId" width="180" show-overflow-tooltip />
+      <el-table-column label="创建时间" align="center" prop="createTime" width="180">
+        <template slot-scope="scope">
+          <span>{{ parseTime(scope.row.createTime) }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="180" fixed="right">
+        <template slot-scope="scope">
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-edit"
+            @click="handleUpdate(scope.row)"
+          >修改</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-delete"
+            @click="handleDelete(scope.row)"
+          >删除</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <pagination
+      v-show="total>0"
+      :total="total"
+      :page.sync="queryParams.current"
+      :limit.sync="queryParams.size"
+      @pagination="getList"
+    />
+
+    <!-- 添加或修改推广账号对话框 -->
+    <el-dialog :title="title" :visible.sync="open" width="680px" append-to-body class="promotion-dialog">
+      <el-form ref="form" :model="form" :rules="rules" label-width="140px" class="elegant-form">
+        <!-- 基础信息 -->
+        <div class="form-section">
+          <div class="section-title">
+            <i class="el-icon-info"></i>
+            <span>基础信息</span>
+          </div>
+          <el-form-item label="推广方式" prop="promotionType">
+            <el-input v-model="form.promotionType" disabled placeholder="展示类" />
+          </el-form-item>
+          <el-form-item label="广告商" prop="advertiserId">
+            <el-select 
+              v-model="form.advertiserId" 
+              placeholder="请选择广告商" 
+              style="width: 100%" 
+              filterable
+              @change="handleAdvertiserChange"
+              :disabled="isEdit"
+            >
+              <el-option
+                v-for="item in advertiserOptions"
+                :key="item.id"
+                :label="item.advertiserName"
+                :value="item.id"
+              />
+            </el-select>
+          </el-form-item>
+          <el-form-item label="配置方式" prop="configMode">
+            <el-radio-group v-model="form.configMode">
+              <el-radio :label="1" :border="true">服务模式</el-radio>
+              <el-radio :label="2" :border="true">广告主模式</el-radio>
+            </el-radio-group>
+          </el-form-item>
+        </div>
+
+        <!-- 账号信息 -->
+        <div class="form-section">
+          <div class="section-title">
+            <i class="el-icon-user"></i>
+            <span>账号信息</span>
+          </div>
+          <el-form-item label="推广账号名称" prop="accountName">
+            <el-input 
+              v-model="form.accountName" 
+              placeholder="请输入推广账号名称"
+              prefix-icon="el-icon-user-solid"
+              clearable
+              :disabled="isEdit"
+            />
+          </el-form-item>
+          <el-form-item label="推广账户简称" prop="accountShortName">
+            <el-input 
+              v-model="form.accountShortName" 
+              placeholder="请输入推广账户简称"
+              prefix-icon="el-icon-tickets"
+              clearable
+            />
+          </el-form-item>
+        </div>
+
+        <!-- API配置 -->
+        <div class="form-section">
+          <div class="section-title">
+            <i class="el-icon-setting"></i>
+            <span>API配置</span>
+          </div>
+          <el-form-item label="API读取账号信息" prop="apiSwitch">
+            <el-radio-group v-model="form.apiSwitch">
+              <el-radio :label="1" :border="true">开</el-radio>
+              <el-radio :label="2" :border="true">关</el-radio>
+            </el-radio-group>
+          </el-form-item>
+          
+          <template v-if="form.apiSwitch === 1">
+            <el-form-item label="AppID" prop="appId" class="slide-fade">
+              <el-input 
+                v-model="form.appId" 
+                placeholder="请输入AppID"
+                prefix-icon="el-icon-key"
+                clearable
+              />
+            </el-form-item>
+            <el-form-item label="Secret Key" prop="appSecret" class="slide-fade">
+              <el-input 
+                v-model="form.appSecret" 
+                placeholder="请输入Secret Key"
+                prefix-icon="el-icon-lock"
+                type="password"
+                show-password
+                clearable
+              />
+            </el-form-item>
+            <el-form-item label="广告主id" prop="adAccountId" class="slide-fade">
+              <el-input 
+                v-model="form.adAccountId" 
+                placeholder="请输入广告主id"
+                prefix-icon="el-icon-postcard"
+                clearable
+              />
+            </el-form-item>
+            <el-form-item label="回调地址" prop="callbackUrl" class="slide-fade">
+              <el-input 
+                v-model="form.callbackUrl" 
+                placeholder="请输入回调地址"
+                prefix-icon="el-icon-link"
+                clearable
+              />
+            </el-form-item>
+            <el-form-item label="应用授权链接" prop="authUrl" class="slide-fade">
+              <el-input 
+                v-model="form.authUrl" 
+                placeholder="请输入应用授权链接"
+                prefix-icon="el-icon-connection"
+                clearable
+              />
+            </el-form-item>
+          </template>
+        </div>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button @click="cancel">取 消</el-button>
+        <el-button type="primary" @click="submitForm" icon="el-icon-check">确 定</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { pagePromotionAccount, getPromotionAccount, addPromotionAccount, updatePromotionAccount, delPromotionAccount, batchDelPromotionAccount } from "@/api/adv/promotionAccount";
+import { pageAdvertiser } from "@/api/adv/advertiser";
+
+export default {
+  name: "PromotionAccount",
+  data() {
+    return {
+      // 遮罩层
+      loading: true,
+      // 选中数组
+      ids: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 推广账号表格数据
+      accountList: [],
+      // 广告商选项
+      advertiserOptions: [],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      // 是否为编辑模式
+      isEdit: false,
+      // 查询参数
+      queryParams: {
+        current: 1,
+        size: 10,
+        accountName: undefined,
+        advertiserId: undefined
+      },
+      // 表单参数
+      form: {},
+      // 表单校验
+      rules: {
+        promotionType: [
+          { required: true, message: "推广方式不能为空", trigger: "blur" }
+        ],
+        advertiserId: [
+          { required: true, message: "请选择广告商", trigger: "change" }
+        ],
+        configMode: [
+          { required: true, message: "请选择配置方式", trigger: "change" }
+        ],
+        accountName: [
+          { required: true, message: "推广账号名称不能为空", trigger: "blur" }
+        ],
+        accountShortName: [
+          { required: true, message: "推广账户简称不能为空", trigger: "blur" }
+        ],
+        apiSwitch: [
+          { required: true, message: "请选择API读取账号信息", trigger: "change" }
+        ],
+        appId: [
+          { 
+            validator: (rule, value, callback) => {
+              if (this.form.apiSwitch === 1 && !value) {
+                callback(new Error('请输入AppID'));
+              } else {
+                callback();
+              }
+            }, 
+            trigger: "blur" 
+          }
+        ],
+        appSecret: [
+          { 
+            validator: (rule, value, callback) => {
+              if (this.form.apiSwitch === 1 && !value) {
+                callback(new Error('请输入Secret Key'));
+              } else {
+                callback();
+              }
+            }, 
+            trigger: "blur" 
+          }
+        ],
+        adAccountId: [
+          { 
+            validator: (rule, value, callback) => {
+              if (this.form.apiSwitch === 1 && !value) {
+                callback(new Error('请输入广告主id'));
+              } else {
+                callback();
+              }
+            }, 
+            trigger: "blur" 
+          }
+        ]
+      }
+    };
+  },
+  created() {
+    this.getList();
+    this.getAdvertiserOptions();
+  },
+  methods: {
+    /** 查询广告商选项 */
+    getAdvertiserOptions() {
+      pageAdvertiser({ pageNum: 1, pageSize: 1000 }).then(response => {
+        this.advertiserOptions = response.data.records;
+      });
+    },
+    /** 查询推广账号列表 */
+    getList() {
+      this.loading = true;
+      pagePromotionAccount(this.queryParams).then(response => {
+        this.accountList = response.data.records;
+        this.total = response.data.total;
+        this.loading = false;
+      });
+    },
+    // 取消按钮
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        id: undefined,
+        promotionType: "展示类",
+        advertiserId: undefined,
+        advertiserName: undefined,
+        configMode: 1,
+        accountName: undefined,
+        accountShortName: undefined,
+        apiSwitch: 2,
+        appId: undefined,
+        appSecret: undefined,
+        adAccountId: undefined,
+        callbackUrl: undefined,
+        authUrl: undefined
+      };
+      this.resetForm("form");
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.current = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm("queryForm");
+      this.handleQuery();
+    },
+    // 多选框选中数据
+    handleSelectionChange(selection) {
+      this.ids = selection.map(item => item.id);
+      this.single = selection.length !== 1;
+      this.multiple = !selection.length;
+    },
+    /** 新增按钮操作 */
+    handleAdd() {
+      this.reset();
+      this.isEdit = false;
+      this.open = true;
+      this.title = "添加推广账号";
+    },
+    /** 修改按钮操作 */
+    handleUpdate(row) {
+      this.reset();
+      this.isEdit = true;
+      const id = row.id || this.ids[0];
+      getPromotionAccount(id).then(response => {
+        this.form = response.data;
+        this.open = true;
+        this.title = "修改推广账号";
+      });
+    },
+    /** 提交按钮 */
+    submitForm() {
+      this.$refs["form"].validate(valid => {
+        if (valid) {
+          if (this.form.id != undefined) {
+            updatePromotionAccount(this.form.id, this.form).then(response => {
+              if (response.code === 200) {
+                this.msgSuccess("修改成功");
+                this.open = false;
+                this.getList();
+              }
+            });
+          } else {
+            addPromotionAccount(this.form).then(response => {
+              if (response.code === 200) {
+                this.msgSuccess("新增成功");
+                this.open = false;
+                this.getList();
+              }
+            });
+          }
+        }
+      });
+    },
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      const ids = row.id ? [row.id] : this.ids;
+      this.$confirm('是否确认删除选中的推广账号?', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(() => {
+        return batchDelPromotionAccount(ids);
+      }).then(() => {
+        this.getList();
+        this.msgSuccess("删除成功");
+      }).catch(function() {});
+    },
+    /** 广告商变化时 */
+    handleAdvertiserChange(advertiserId) {
+      this.form.advertiserName = "";
+      if (advertiserId) {
+        const advertiser = this.advertiserOptions.find(item => item.id === advertiserId);
+        if (advertiser) {
+          this.form.advertiserName = advertiser.advertiserName;
+        }
+      }
+    }
+  }
+};
+</script>
+
+<style lang="scss" scoped>
+// 对话框样式
+::v-deep .promotion-dialog {
+  .el-dialog__header {
+    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+    padding: 20px;
+    margin: 0;
+    border-radius: 4px 4px 0 0;
+    
+    .el-dialog__title {
+      color: #fff;
+      font-size: 18px;
+      font-weight: 500;
+    }
+    
+    .el-dialog__headerbtn .el-dialog__close {
+      color: #fff;
+      font-size: 20px;
+      
+      &:hover {
+        color: #f0f0f0;
+      }
+    }
+  }
+  
+  .el-dialog__body {
+    padding: 25px 30px;
+    background-color: #f8f9fa;
+    max-height: 70vh;
+    overflow-y: auto;
+  }
+  
+  .el-dialog__footer {
+    padding: 15px 30px;
+    border-top: 1px solid #e8e8e8;
+    background-color: #fff;
+  }
+}
+
+// 表单样式
+.elegant-form {
+  .form-section {
+    background: #fff;
+    border-radius: 8px;
+    padding: 20px;
+    margin-bottom: 20px;
+    box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
+    transition: all 0.3s ease;
+    
+    &:hover {
+      box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
+      transform: translateY(-2px);
+    }
+    
+    &:last-child {
+      margin-bottom: 0;
+    }
+  }
+  
+  .section-title {
+    display: flex;
+    align-items: center;
+    margin-bottom: 20px;
+    padding-bottom: 12px;
+    border-bottom: 2px solid #e8e8e8;
+    font-size: 15px;
+    font-weight: 600;
+    color: #303133;
+    
+    i {
+      font-size: 18px;
+      margin-right: 8px;
+      background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+      -webkit-background-clip: text;
+      -webkit-text-fill-color: transparent;
+      background-clip: text;
+    }
+    
+    span {
+      position: relative;
+      
+      &::after {
+        content: '';
+        position: absolute;
+        left: 0;
+        bottom: -12px;
+        width: 0;
+        height: 2px;
+        background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+        transition: width 0.3s ease;
+      }
+    }
+  }
+  
+  .form-section:hover .section-title span::after {
+    width: 100%;
+  }
+  
+  .el-form-item {
+    margin-bottom: 20px;
+    
+    &:last-child {
+      margin-bottom: 0;
+    }
+  }
+  
+  ::v-deep .el-form-item__label {
+    color: #606266;
+    font-weight: 500;
+    font-size: 14px;
+  }
+  
+  ::v-deep .el-input__inner,
+  ::v-deep .el-textarea__inner {
+    border-radius: 6px;
+    border: 1px solid #dcdfe6;
+    transition: all 0.3s ease;
+    
+    &:hover {
+      border-color: #c0c4cc;
+    }
+    
+    &:focus {
+      border-color: #667eea;
+      box-shadow: 0 0 0 2px rgba(102, 126, 234, 0.1);
+    }
+    
+    &:disabled {
+      background-color: #f5f7fa;
+      color: #909399;
+    }
+  }
+  
+  ::v-deep .el-radio {
+    margin-right: 20px;
+    
+    &.is-bordered {
+      border-radius: 6px;
+      padding: 10px 20px;
+      transition: all 0.3s ease;
+      
+      &:hover {
+        border-color: #667eea;
+      }
+      
+      &.is-checked {
+        border-color: #667eea;
+        background-color: rgba(102, 126, 234, 0.05);
+      }
+    }
+  }
+  
+  // API字段显示动画
+  .slide-fade {
+    animation: slideDown 0.3s ease;
+  }
+}
+
+@keyframes slideDown {
+  from {
+    opacity: 0;
+    transform: translateY(-10px);
+  }
+  to {
+    opacity: 1;
+    transform: translateY(0);
+  }
+}
+
+// 按钮样式
+.dialog-footer {
+  text-align: right;
+  
+  .el-button {
+    padding: 10px 24px;
+    border-radius: 6px;
+    font-weight: 500;
+    transition: all 0.3s ease;
+    
+    &.el-button--primary {
+      background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+      border: none;
+      
+      &:hover {
+        transform: translateY(-2px);
+        box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
+      }
+      
+      &:active {
+        transform: translateY(0);
+      }
+    }
+    
+    &.el-button--default {
+      &:hover {
+        color: #667eea;
+        border-color: #667eea;
+        background-color: rgba(102, 126, 234, 0.05);
+      }
+    }
+  }
+}
+</style>
+

+ 881 - 0
src/views/adv/site/index.vue

@@ -0,0 +1,881 @@
+<template>
+  <div class="app-container">
+    <el-row :gutter="10" class="mb8">
+      <el-col :span="1.5">
+        <el-button
+          plain
+          type="primary"
+          icon="el-icon-plus"
+          size="mini"
+          @click="handleAdd"
+        >新增</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          plain
+          type="success"
+          icon="el-icon-refresh"
+          size="mini"
+          @click="getList"
+        >刷新</el-button>
+      </el-col>
+    </el-row>
+
+    <el-table border v-loading="loading" :data="siteList">
+      <el-table-column label="ID" align="center" prop="id" width="80" />
+      <el-table-column label="站点名称" align="center" prop="siteName" width="150" show-overflow-tooltip />
+      <el-table-column label="站点地址" align="center" prop="siteUrl" width="180" show-overflow-tooltip />
+      <el-table-column label="投放类型" align="center" prop="launchType" width="100" />
+      <el-table-column label="广告类型" align="center" prop="adType" width="120" />
+      <el-table-column label="投放广告商" align="center" prop="advertiserName" width="150" show-overflow-tooltip />
+      <el-table-column label="投放广告商账号" align="center" prop="promotionAccountName" width="150" show-overflow-tooltip />
+      <el-table-column label="投放页面" align="center" prop="launchPageName" width="150" show-overflow-tooltip />
+      <el-table-column label="来源" align="center" prop="sourceName" width="120" show-overflow-tooltip />
+      <el-table-column label="投放域名" align="center" prop="launchDomain" width="150" show-overflow-tooltip />
+      <el-table-column label="配置回传" align="center" prop="configCallback" width="100">
+        <template slot-scope="scope">
+          <el-tag :type="scope.row.configCallback === 1 ? 'success' : 'info'" size="small">
+            {{ scope.row.configCallback === 1 ? '是' : '否' }}
+          </el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="回传账号" align="center" prop="callbackAccountName" width="150" show-overflow-tooltip />
+      <el-table-column label="创建时间" align="center" prop="createTime" width="180">
+        <template slot-scope="scope">
+          <span>{{ parseTime(scope.row.createTime) }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="280" fixed="right">
+        <template slot-scope="scope">
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-edit"
+            @click="handleUpdate(scope.row)"
+          >修改</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-data-line"
+            @click="handleStatistics(scope.row)"
+          >统计</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-delete"
+            @click="handleDelete(scope.row)"
+          >删除</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <!-- 添加或修改站点对话框 -->
+    <el-dialog :title="title" :visible.sync="open" width="680px" append-to-body class="site-dialog">
+      <el-form ref="form" :model="form" :rules="rules" label-width="130px" class="elegant-form">
+        <!-- 基础信息 -->
+        <div class="form-section">
+          <div class="section-title">
+            <i class="el-icon-info"></i>
+            <span>基础信息</span>
+          </div>
+          <el-form-item label="站点名称" prop="siteName">
+            <el-input 
+              v-model="form.siteName" 
+              placeholder="请输入站点名称"
+              prefix-icon="el-icon-notebook-2"
+              clearable
+            />
+          </el-form-item>
+          <el-form-item label="站点地址" prop="siteUrl">
+            <el-input 
+              v-model="form.siteUrl" 
+              placeholder="请输入站点地址"
+              prefix-icon="el-icon-link"
+              clearable
+            />
+          </el-form-item>
+        </div>
+
+        <!-- 投放配置 -->
+        <div class="form-section">
+          <div class="section-title">
+            <i class="el-icon-setting"></i>
+            <span>投放配置</span>
+          </div>
+          <el-form-item label="投放类型" prop="launchType">
+            <el-select 
+              v-model="form.launchType" 
+              placeholder="请选择投放类型" 
+              style="width: 100%"
+              prefix-icon="el-icon-s-flag"
+            >
+              <el-option
+                v-for="item in launchTypeOptions"
+                :key="item.value"
+                :label="item.label"
+                :value="item.value"
+              />
+            </el-select>
+          </el-form-item>
+          <el-form-item label="广告类型" prop="adType">
+            <el-select 
+              v-model="form.adType" 
+              placeholder="请选择广告类型" 
+              style="width: 100%"
+            >
+              <el-option
+                v-for="item in adTypeOptions"
+                :key="item.value"
+                :label="item.label"
+                :value="item.value"
+              />
+            </el-select>
+          </el-form-item>
+        </div>
+
+        <!-- 广告商信息 -->
+        <div class="form-section">
+          <div class="section-title">
+            <i class="el-icon-office-building"></i>
+            <span>广告商信息</span>
+          </div>
+          <el-form-item label="投放广告商" prop="advertiserId">
+            <el-select 
+              v-model="form.advertiserId" 
+              placeholder="请选择投放广告商" 
+              style="width: 100%"
+              @change="handleAdvertiserChange"
+              filterable
+            >
+              <el-option
+                v-for="item in advertiserList"
+                :key="item.id"
+                :label="item.advertiserName"
+                :value="item.id"
+              />
+            </el-select>
+          </el-form-item>
+          <el-form-item label="投放广告商账号" prop="promotionAccountId">
+            <el-select 
+              v-model="form.promotionAccountId" 
+              placeholder="请选择投放广告商账号" 
+              style="width: 100%"
+              @change="handlePromotionAccountChange"
+              filterable
+              :disabled="!form.advertiserId"
+            >
+              <el-option
+                v-for="item in promotionAccountList"
+                :key="item.id"
+                :label="item.accountName"
+                :value="item.id"
+              />
+            </el-select>
+          </el-form-item>
+        </div>
+
+        <!-- 页面和来源 -->
+        <div class="form-section">
+          <div class="section-title">
+            <i class="el-icon-monitor"></i>
+            <span>页面和来源</span>
+          </div>
+          <el-form-item label="投放页面" prop="launchPageId">
+            <el-select 
+              v-model="form.launchPageId" 
+              placeholder="请选择投放页面" 
+              style="width: 100%"
+              @change="handleLaunchPageChange"
+              filterable
+            >
+              <el-option
+                v-for="item in landingPageTemplateList"
+                :key="item.id"
+                :label="item.templateName"
+                :value="item.id"
+              />
+            </el-select>
+          </el-form-item>
+          <el-form-item label="投放域名" prop="launchDomain">
+            <el-select 
+              v-model="form.launchDomain" 
+              placeholder="请选择投放域名" 
+              style="width: 100%"
+              filterable
+            >
+              <el-option
+                v-for="item in launchDomainList"
+                :key="item.id"
+                :label="item.domain"
+                :value="item.domain"
+              />
+            </el-select>
+          </el-form-item>
+          <el-form-item label="来源" prop="sourceId">
+            <el-select 
+              v-model="form.sourceId" 
+              placeholder="请选择来源" 
+              style="width: 100%"
+              @change="handleSourceChange"
+              filterable
+            >
+              <el-option
+                v-for="item in leadSourceList"
+                :key="item.id"
+                :label="item.sourceName"
+                :value="item.id"
+              />
+            </el-select>
+          </el-form-item>
+        </div>
+
+        <!-- 回传配置 -->
+        <div class="form-section">
+          <div class="section-title">
+            <i class="el-icon-share"></i>
+            <span>回传配置</span>
+          </div>
+          <el-form-item label="是否配置回传" prop="configCallback">
+            <el-radio-group v-model="form.configCallback">
+              <el-radio 
+                v-for="item in configCallbackOptions"
+                :key="item.value"
+                :label="item.value"
+                :border="true"
+              >{{ item.label }}</el-radio>
+            </el-radio-group>
+          </el-form-item>
+          <el-form-item 
+            v-if="form.configCallback === 1" 
+            label="回传账号" 
+            prop="callbackAccountId"
+            class="slide-fade"
+          >
+            <el-select 
+              v-model="form.callbackAccountId" 
+              placeholder="请选择回传账号" 
+              style="width: 100%"
+              @change="handleCallbackAccountChange"
+              filterable
+            >
+              <el-option
+                v-for="item in callbackAccountList"
+                :key="item.id"
+                :label="item.callbackAccountName"
+                :value="item.id"
+              />
+            </el-select>
+          </el-form-item>
+        </div>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button @click="cancel">取 消</el-button>
+        <el-button type="primary" @click="submitForm" icon="el-icon-check">确 定</el-button>
+      </div>
+    </el-dialog>
+
+    <!-- 站点统计对话框 -->
+    <el-dialog title="站点统计" :visible.sync="statisticsOpen" width="800px" append-to-body>
+      <el-descriptions :column="2" border v-if="statistics">
+        <el-descriptions-item label="站点ID">{{ statistics.siteId }}</el-descriptions-item>
+        <el-descriptions-item label="访问量">{{ statistics.visitCount || 0 }}</el-descriptions-item>
+        <el-descriptions-item label="访客数">{{ statistics.visitorCount || 0 }}</el-descriptions-item>
+        <el-descriptions-item label="转化数">{{ statistics.conversionCount || 0 }}</el-descriptions-item>
+        <el-descriptions-item label="转化率">
+          {{ statistics.conversionRate ? (statistics.conversionRate * 100).toFixed(2) + '%' : '0%' }}
+        </el-descriptions-item>
+        <el-descriptions-item label="统计时间">{{ parseTime(statistics.updateTime) }}</el-descriptions-item>
+      </el-descriptions>
+      <div v-else style="text-align: center; padding: 40px 0;">
+        <el-empty description="暂无统计数据"></el-empty>
+      </div>
+      <div slot="footer" class="dialog-footer">
+        <el-button @click="statisticsOpen = false">关 闭</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { listSite, getSite, addSite, updateSite, delSite, getSiteStatistics } from "@/api/adv/site";
+import { pageAdvertiser } from "@/api/adv/advertiser";
+import { pagePromotionAccount } from "@/api/adv/promotionAccount";
+import { pageTemplate } from "@/api/adv/landingPageTemplate";
+import { pageLeadSource } from "@/api/adv/leadSource";
+import { pageCallbackAccount } from "@/api/adv/callbackAccount";
+import { pageDomain } from "@/api/adv/domain";
+
+export default {
+  name: "Site",
+  data() {
+    return {
+      // 遮罩层
+      loading: true,
+      // 站点表格数据
+      siteList: [],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      // 是否显示统计弹出层
+      statisticsOpen: false,
+      // 统计数据
+      statistics: null,
+      // 表单参数
+      form: {},
+      // 投放类型选项
+      launchTypeOptions: [
+        { label: "线上投放", value: "线上投放" },
+        { label: "线下投放", value: "线下投放" }
+      ],
+      // 广告类型选项
+      adTypeOptions: [
+        { label: "信息流广告", value: "信息流广告" }
+      ],
+      // 是否配置回传选项
+      configCallbackOptions: [
+        { label: "否", value: 0 },
+        { label: "是", value: 1 }
+      ],
+      // 广告商列表
+      advertiserList: [],
+      // 广告商账号列表
+      promotionAccountList: [],
+      // 落地页模板列表
+      landingPageTemplateList: [],
+      // 线索来源列表
+      leadSourceList: [],
+      // 回传账号列表
+      callbackAccountList: [],
+      // 投放域名列表
+      launchDomainList: [],
+      // 表单校验
+      rules: {
+        siteName: [
+          { required: true, message: "站点名称不能为空", trigger: "blur" }
+        ],
+        siteUrl: [
+          { required: true, message: "站点地址不能为空", trigger: "blur" }
+        ],
+        launchType: [
+          { required: true, message: "请选择投放类型", trigger: "change" }
+        ],
+        adType: [
+          { required: true, message: "请选择广告类型", trigger: "change" }
+        ],
+        advertiserId: [
+          { required: true, message: "请选择投放广告商", trigger: "change" }
+        ],
+        promotionAccountId: [
+          { required: true, message: "请选择投放广告商账号", trigger: "change" }
+        ],
+        launchPageId: [
+          { required: true, message: "请选择投放页面", trigger: "change" }
+        ],
+        sourceId: [
+          { required: true, message: "请选择来源", trigger: "change" }
+        ],
+        launchDomain: [
+          { required: true, message: "请选择投放域名", trigger: "change" }
+        ],
+        configCallback: [
+          { required: true, message: "请选择是否配置回传", trigger: "change" }
+        ],
+        callbackAccountId: [
+          { 
+            validator: (rule, value, callback) => {
+              if (this.form.configCallback === 1 && !value) {
+                callback(new Error('请选择回传账号'));
+              } else {
+                callback();
+              }
+            }, 
+            trigger: "change" 
+          }
+        ]
+      }
+    };
+  },
+  created() {
+    this.getList();
+  },
+  methods: {
+    /** 查询站点列表 */
+    getList() {
+      this.loading = true;
+      listSite().then(response => {
+        this.siteList = response.data;
+        this.loading = false;
+      });
+    },
+    // 取消按钮
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        id: undefined,
+        siteName: undefined,
+        siteUrl: undefined,
+        launchType: undefined,
+        adType: undefined,
+        advertiserId: undefined,
+        advertiserName: undefined,
+        promotionAccountId: undefined,
+        promotionAccountName: undefined,
+        launchPageId: undefined,
+        launchPageName: undefined,
+        sourceId: undefined,
+        sourceName: undefined,
+        launchDomain: undefined,
+        configCallback: 0,
+        callbackAccountId: undefined,
+        callbackAccountName: undefined
+      };
+      this.promotionAccountList = [];
+      this.launchDomainList = [];
+      this.resetForm("form");
+    },
+    /** 新增按钮操作 */
+    handleAdd() {
+      this.reset();
+      this.loadSelectOptions();
+      this.open = true;
+      this.title = "添加站点";
+    },
+    /** 修改按钮操作 */
+    handleUpdate(row) {
+      this.reset();
+      this.loadSelectOptions();
+      getSite(row.id).then(response => {
+        this.form = response.data;
+        // 如果有广告商ID,加载对应的广告商账号列表
+        if (this.form.advertiserId) {
+          this.loadPromotionAccountList(this.form.advertiserId);
+        }
+        this.open = true;
+        this.title = "修改站点";
+      });
+    },
+    /** 查看统计按钮操作 */
+    handleStatistics(row) {
+      this.statistics = null;
+      this.statisticsOpen = true;
+      getSiteStatistics(row.id).then(response => {
+        this.statistics = response.data;
+      }).catch(() => {
+        this.statistics = null;
+      });
+    },
+    /** 提交按钮 */
+    submitForm() {
+      this.$refs["form"].validate(valid => {
+        if (valid) {
+          if (this.form.id != undefined) {
+            updateSite(this.form.id, this.form).then(response => {
+              if (response.code === 200) {
+                this.msgSuccess("修改成功");
+                this.open = false;
+                this.getList();
+              }
+            });
+          } else {
+            addSite(this.form).then(response => {
+              if (response.code === 200) {
+                this.msgSuccess("新增成功");
+                this.open = false;
+                this.getList();
+              }
+            });
+          }
+        }
+      });
+    },
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      this.$confirm('是否确认删除站点"' + row.siteName + '"?', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(() => {
+        return delSite(row.id);
+      }).then(() => {
+        this.getList();
+        this.msgSuccess("删除成功");
+      }).catch(function() {});
+    },
+    /** 加载下拉选项数据 */
+    loadSelectOptions() {
+      // 加载广告商列表
+      pageAdvertiser({ pageNum: 1, pageSize: 1000 }).then(response => {
+        this.advertiserList = response.data.records;
+      }).catch(error => {
+        console.error('加载广告商列表失败:', error);
+        this.advertiserList = [];
+      });
+      
+      // 加载落地页模板列表
+      pageTemplate({ pageNum: 1, pageSize: 1000, status: 1 }).then(response => {
+        this.landingPageTemplateList = response.data.records;
+      }).catch(error => {
+        console.error('加载落地页模板失败:', error);
+        this.landingPageTemplateList = [];
+      });
+      
+      // 加载线索来源列表
+      pageLeadSource({ pageNum: 1, pageSize: 1000 }).then(response => {
+        this.leadSourceList = response.data.records;
+      }).catch(error => {
+        console.error('加载线索来源失败:', error);
+        this.leadSourceList = [];
+      });
+      
+      // 加载回传账号列表
+      pageCallbackAccount({ pageNum: 1, pageSize: 1000 }).then(response => {
+        this.callbackAccountList = response.data.records;
+      }).catch(error => {
+        console.error('加载回传账号失败:', error);
+        this.callbackAccountList = [];
+      });
+      
+      // 加载域名列表
+      pageDomain({ pageNum: 1, pageSize: 1000, status: 1 }).then(response => {
+        this.launchDomainList = response.data.records;
+      }).catch(error => {
+        console.error('加载域名失败:', error);
+        this.launchDomainList = [];
+      });
+    },
+    /** 广告商变化时 */
+    handleAdvertiserChange(advertiserId) {
+      this.form.advertiserName = "";
+      this.form.promotionAccountId = undefined;
+      this.form.promotionAccountName = "";
+      this.promotionAccountList = [];
+      
+      if (advertiserId) {
+        const advertiser = this.advertiserList.find(item => item.id === advertiserId);
+        if (advertiser) {
+          this.form.advertiserName = advertiser.advertiserName;
+        }
+        // 加载对应的广告商账号列表
+        this.loadPromotionAccountList(advertiserId);
+      }
+    },
+    /** 加载广告商账号列表 */
+    loadPromotionAccountList(advertiserId) {
+      if (!advertiserId) return;
+      pagePromotionAccount({ pageNum: 1, pageSize: 1000, advertiserId: advertiserId }).then(response => {
+        this.promotionAccountList = response.data.records;
+      }).catch(error => {
+        console.error('加载广告商账号失败:', error);
+        this.promotionAccountList = [];
+      });
+    },
+    /** 广告商账号变化时 */
+    handlePromotionAccountChange(promotionAccountId) {
+      this.form.promotionAccountName = "";
+      if (promotionAccountId) {
+        const account = this.promotionAccountList.find(item => item.id === promotionAccountId);
+        if (account) {
+          this.form.promotionAccountName = account.accountName;
+        }
+      }
+    },
+    /** 投放页面变化时 */
+    handleLaunchPageChange(launchPageId) {
+      this.form.launchPageName = "";
+      
+      if (launchPageId) {
+        const template = this.landingPageTemplateList.find(item => item.id === launchPageId);
+        if (template) {
+          this.form.launchPageName = template.templateName;
+        }
+      }
+    },
+    /** 线索来源变化时 */
+    handleSourceChange(sourceId) {
+      this.form.sourceName = "";
+      if (sourceId) {
+        const source = this.leadSourceList.find(item => item.id === sourceId);
+        if (source) {
+          this.form.sourceName = source.sourceName;
+        }
+      }
+    },
+    /** 回传账号变化时 */
+    handleCallbackAccountChange(callbackAccountId) {
+      this.form.callbackAccountName = "";
+      if (callbackAccountId) {
+        const account = this.callbackAccountList.find(item => item.id === callbackAccountId);
+        if (account) {
+          this.form.callbackAccountName = account.callbackAccountName;
+        }
+      }
+    }
+  }
+};
+</script>
+
+<style lang="scss" scoped>
+.app-container {
+  padding: 20px;
+}
+
+// 对话框样式
+::v-deep .site-dialog {
+  .el-dialog__header {
+    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+    padding: 20px;
+    margin: 0;
+    border-radius: 4px 4px 0 0;
+    
+    .el-dialog__title {
+      color: #fff;
+      font-size: 18px;
+      font-weight: 500;
+    }
+    
+    .el-dialog__headerbtn .el-dialog__close {
+      color: #fff;
+      font-size: 20px;
+      
+      &:hover {
+        color: #f0f0f0;
+      }
+    }
+  }
+  
+  .el-dialog__body {
+    padding: 25px 30px;
+    background-color: #f8f9fa;
+  }
+  
+  .el-dialog__footer {
+    padding: 15px 30px;
+    border-top: 1px solid #e8e8e8;
+    background-color: #fff;
+  }
+}
+
+// 表单样式
+.elegant-form {
+  .form-section {
+    background: #fff;
+    border-radius: 8px;
+    padding: 20px;
+    margin-bottom: 20px;
+    box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
+    transition: all 0.3s ease;
+    
+    &:hover {
+      box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
+      transform: translateY(-2px);
+    }
+    
+    &:last-child {
+      margin-bottom: 0;
+    }
+  }
+  
+  .section-title {
+    display: flex;
+    align-items: center;
+    margin-bottom: 20px;
+    padding-bottom: 12px;
+    border-bottom: 2px solid #e8e8e8;
+    font-size: 15px;
+    font-weight: 600;
+    color: #303133;
+    
+    i {
+      font-size: 18px;
+      margin-right: 8px;
+      background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+      -webkit-background-clip: text;
+      -webkit-text-fill-color: transparent;
+      background-clip: text;
+    }
+    
+    span {
+      position: relative;
+      
+      &::after {
+        content: '';
+        position: absolute;
+        left: 0;
+        bottom: -12px;
+        width: 0;
+        height: 2px;
+        background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+        transition: width 0.3s ease;
+      }
+    }
+  }
+  
+  .form-section:hover .section-title span::after {
+    width: 100%;
+  }
+  
+  .el-form-item {
+    margin-bottom: 20px;
+    
+    &:last-child {
+      margin-bottom: 0;
+    }
+  }
+  
+  ::v-deep .el-form-item__label {
+    color: #606266;
+    font-weight: 500;
+    font-size: 14px;
+  }
+  
+  ::v-deep .el-input__inner,
+  ::v-deep .el-textarea__inner {
+    border-radius: 6px;
+    border: 1px solid #dcdfe6;
+    transition: all 0.3s ease;
+    
+    &:hover {
+      border-color: #c0c4cc;
+    }
+    
+    &:focus {
+      border-color: #667eea;
+      box-shadow: 0 0 0 2px rgba(102, 126, 234, 0.1);
+    }
+  }
+  
+  ::v-deep .el-select {
+    .el-input__inner {
+      padding-left: 15px;
+    }
+  }
+  
+  ::v-deep .el-radio {
+    margin-right: 20px;
+    
+    &.is-bordered {
+      border-radius: 6px;
+      padding: 10px 20px;
+      transition: all 0.3s ease;
+      
+      &:hover {
+        border-color: #667eea;
+      }
+      
+      &.is-checked {
+        border-color: #667eea;
+        background-color: rgba(102, 126, 234, 0.05);
+      }
+    }
+  }
+  
+  // 回传账号显示动画
+  .slide-fade {
+    animation: slideDown 0.3s ease;
+  }
+}
+
+@keyframes slideDown {
+  from {
+    opacity: 0;
+    transform: translateY(-10px);
+  }
+  to {
+    opacity: 1;
+    transform: translateY(0);
+  }
+}
+
+// 按钮样式优化
+.dialog-footer {
+  text-align: right;
+  
+  .el-button {
+    padding: 10px 24px;
+    border-radius: 6px;
+    font-weight: 500;
+    transition: all 0.3s ease;
+    
+    &.el-button--primary {
+      background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+      border: none;
+      
+      &:hover {
+        transform: translateY(-2px);
+        box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
+      }
+      
+      &:active {
+        transform: translateY(0);
+      }
+    }
+    
+    &.el-button--default {
+      &:hover {
+        color: #667eea;
+        border-color: #667eea;
+        background-color: rgba(102, 126, 234, 0.05);
+      }
+    }
+  }
+}
+
+// 表格样式优化
+.el-table {
+  border-radius: 8px;
+  overflow: hidden;
+  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
+  
+  ::v-deep .el-table__header {
+    th {
+      background-color: #f5f7fa;
+      color: #606266;
+      font-weight: 600;
+      font-size: 14px;
+    }
+  }
+  
+  ::v-deep .el-table__row {
+    transition: all 0.3s ease;
+    
+    &:hover {
+      background-color: rgba(102, 126, 234, 0.05);
+    }
+  }
+}
+
+// 顶部按钮组样式
+.mb8 {
+  margin-bottom: 15px;
+  
+  .el-button {
+    border-radius: 6px;
+    padding: 10px 20px;
+    font-weight: 500;
+    transition: all 0.3s ease;
+    
+    &.el-button--primary {
+      background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+      border: none;
+      
+      &:hover {
+        transform: translateY(-2px);
+        box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
+      }
+    }
+    
+    &.el-button--success {
+      &:hover {
+        transform: translateY(-2px);
+        box-shadow: 0 4px 12px rgba(103, 194, 58, 0.4);
+      }
+    }
+  }
+}
+</style>
+

+ 220 - 0
src/views/adv/trackingLink/index.vue

@@ -0,0 +1,220 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch">
+      <el-form-item label="链接名称" prop="trackingName">
+        <el-input
+          v-model="queryParams.trackingName"
+          placeholder="请输入链接名称"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="广告商" prop="advertiserId">
+        <el-select v-model="queryParams.advertiserId" placeholder="请选择广告商" clearable size="small" filterable>
+          <el-option
+            v-for="item in advertiserOptions"
+            :key="item.id"
+            :label="item.advertiserName"
+            :value="item.id"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="推广类型" prop="promotionType">
+        <el-input
+          v-model="queryParams.promotionType"
+          placeholder="请输入推广类型"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item>
+        <el-button type="cyan" 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-row :gutter="10" class="mb8">
+      <el-col :span="1.5">
+        <el-button
+          plain
+          type="success"
+          icon="el-icon-refresh"
+          size="mini"
+          @click="getList"
+        >刷新</el-button>
+      </el-col>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+
+    <el-table border v-loading="loading" :data="linkList">
+      <el-table-column label="ID" align="center" prop="id" width="80" />
+      <el-table-column label="链接名称" align="center" prop="trackingName" show-overflow-tooltip />
+      <el-table-column label="广告商" align="center" prop="advertiserId" width="200">
+        <template slot-scope="scope">
+          <span>{{ getAdvertiserName(scope.row.advertiserId) }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="推广类型" align="center" prop="promotionType" width="120" />
+      <el-table-column label="监测链接" align="center" prop="trackingUrl" show-overflow-tooltip min-width="200" />
+      <el-table-column label="创建时间" align="center" prop="createTime" width="180">
+        <template slot-scope="scope">
+          <span>{{ parseTime(scope.row.createTime) }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="150">
+        <template slot-scope="scope">
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-view"
+            @click="handleView(scope.row)"
+          >详情</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-document-copy"
+            @click="handleCopy(scope.row)"
+          >复制链接</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <pagination
+      v-show="total>0"
+      :total="total"
+      :page.sync="queryParams.current"
+      :limit.sync="queryParams.size"
+      @pagination="getList"
+    />
+
+    <!-- 详情对话框 -->
+    <el-dialog title="监测链接详情" :visible.sync="open" width="700px" append-to-body>
+      <el-descriptions :column="1" border v-if="detail">
+        <el-descriptions-item label="链接ID">{{ detail.id }}</el-descriptions-item>
+        <el-descriptions-item label="链接名称">{{ detail.trackingName }}</el-descriptions-item>
+        <el-descriptions-item label="广告商">{{ getAdvertiserName(detail.advertiserId) }}</el-descriptions-item>
+        <el-descriptions-item label="推广类型">{{ detail.promotionType }}</el-descriptions-item>
+        <el-descriptions-item label="监测链接">
+          <el-link :href="detail.trackingUrl" target="_blank" type="primary">{{ detail.trackingUrl }}</el-link>
+        </el-descriptions-item>
+        <el-descriptions-item label="创建时间">{{ parseTime(detail.createTime) }}</el-descriptions-item>
+        <el-descriptions-item label="更新时间">{{ parseTime(detail.updateTime) }}</el-descriptions-item>
+      </el-descriptions>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="handleCopy(detail)">复制链接</el-button>
+        <el-button @click="open = false">关 闭</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { pageTrackingLink, getTrackingLink } from "@/api/adv/trackingLink";
+import { pageAdvertiser } from "@/api/adv/advertiser";
+
+export default {
+  name: "TrackingLink",
+  data() {
+    return {
+      // 遮罩层
+      loading: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 监测链接表格数据
+      linkList: [],
+      // 广告商选项
+      advertiserOptions: [],
+      // 是否显示弹出层
+      open: false,
+      // 详情数据
+      detail: null,
+      // 查询参数
+      queryParams: {
+        current: 1,
+        size: 10,
+        trackingName: undefined,
+        advertiserId: undefined,
+        promotionType: undefined
+      }
+    };
+  },
+  created() {
+    this.getList();
+    this.getAdvertiserOptions();
+  },
+  methods: {
+    /** 查询广告商选项 */
+    getAdvertiserOptions() {
+      pageAdvertiser({ current: 1, size: 1000, enabled: 1 }).then(response => {
+        this.advertiserOptions = response.data.records;
+      });
+    },
+    /** 获取广告商名称 */
+    getAdvertiserName(advertiserId) {
+      const advertiser = this.advertiserOptions.find(item => item.id === advertiserId);
+      return advertiser ? advertiser.advertiserName : advertiserId;
+    },
+    /** 查询监测链接列表 */
+    getList() {
+      this.loading = true;
+      pageTrackingLink(this.queryParams).then(response => {
+        this.linkList = response.data.records;
+        this.total = response.data.total;
+        this.loading = false;
+      });
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.current = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm("queryForm");
+      this.handleQuery();
+    },
+    /** 查看详情按钮操作 */
+    handleView(row) {
+      this.detail = null;
+      this.open = true;
+      getTrackingLink(row.id).then(response => {
+        this.detail = response.data;
+      });
+    },
+    /** 复制链接按钮操作 */
+    handleCopy(row) {
+      const url = row.trackingUrl;
+      if (!url) {
+        this.msgError("链接地址为空");
+        return;
+      }
+      
+      // 创建临时input元素
+      const input = document.createElement('input');
+      input.value = url;
+      document.body.appendChild(input);
+      input.select();
+      
+      try {
+        // 执行复制
+        const successful = document.execCommand('copy');
+        if (successful) {
+          this.msgSuccess("链接已复制到剪贴板");
+        } else {
+          this.msgError("复制失败,请手动复制");
+        }
+      } catch (err) {
+        this.msgError("复制失败,请手动复制");
+      }
+      
+      // 移除临时元素
+      document.body.removeChild(input);
+    }
+  }
+};
+</script>
+