index.vue 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873
  1. <template>
  2. <div class="app-container">
  3. <el-row :gutter="10" class="mb8">
  4. <el-col :span="1.5">
  5. <el-button
  6. plain
  7. type="primary"
  8. icon="el-icon-plus"
  9. size="mini"
  10. @click="handleAdd"
  11. >新增</el-button>
  12. </el-col>
  13. <el-col :span="1.5">
  14. <el-button
  15. plain
  16. type="success"
  17. icon="el-icon-refresh"
  18. size="mini"
  19. @click="getList"
  20. >刷新</el-button>
  21. </el-col>
  22. </el-row>
  23. <el-table border v-loading="loading" :data="siteList">
  24. <el-table-column label="ID" align="center" prop="id" width="80" />
  25. <el-table-column label="站点名称" align="center" prop="siteName" width="150" show-overflow-tooltip />
  26. <el-table-column label="站点地址" align="center" prop="siteUrl" width="180" show-overflow-tooltip />
  27. <el-table-column label="投放类型" align="center" prop="launchType" width="100" />
  28. <el-table-column label="广告类型" align="center" prop="adType" width="120" />
  29. <el-table-column label="投放广告商" align="center" prop="advertiserName" width="150" show-overflow-tooltip />
  30. <el-table-column label="投放广告商账号" align="center" prop="promotionAccountName" width="150" show-overflow-tooltip />
  31. <el-table-column label="投放页面" align="center" prop="launchPageName" width="150" show-overflow-tooltip />
  32. <el-table-column label="来源" align="center" prop="sourceName" width="120" show-overflow-tooltip />
  33. <el-table-column label="投放域名" align="center" prop="launchDomain" width="150" show-overflow-tooltip />
  34. <el-table-column label="配置回传" align="center" prop="configCallback" width="100">
  35. <template slot-scope="scope">
  36. <el-tag :type="scope.row.configCallback === 1 ? 'success' : 'info'" size="small">
  37. {{ scope.row.configCallback === 1 ? '是' : '否' }}
  38. </el-tag>
  39. </template>
  40. </el-table-column>
  41. <el-table-column label="回传账号" align="center" prop="callbackAccountName" width="150" show-overflow-tooltip />
  42. <el-table-column label="创建时间" align="center" prop="createTime" width="180">
  43. <template slot-scope="scope">
  44. <span>{{ parseTime(scope.row.createTime) }}</span>
  45. </template>
  46. </el-table-column>
  47. <el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="280" fixed="right">
  48. <template slot-scope="scope">
  49. <el-button
  50. size="mini"
  51. type="text"
  52. icon="el-icon-edit"
  53. @click="handleUpdate(scope.row)"
  54. >修改</el-button>
  55. <el-button
  56. size="mini"
  57. type="text"
  58. icon="el-icon-data-line"
  59. @click="handleStatistics(scope.row)"
  60. >统计</el-button>
  61. <el-button
  62. size="mini"
  63. type="text"
  64. icon="el-icon-delete"
  65. @click="handleDelete(scope.row)"
  66. >删除</el-button>
  67. </template>
  68. </el-table-column>
  69. </el-table>
  70. <!-- 添加或修改站点对话框 -->
  71. <el-dialog :title="title" :visible.sync="open" width="680px" append-to-body class="site-dialog">
  72. <el-form ref="form" :model="form" :rules="rules" label-width="130px" class="elegant-form">
  73. <!-- 基础信息 -->
  74. <div class="form-section">
  75. <div class="section-title">
  76. <i class="el-icon-info"></i>
  77. <span>基础信息</span>
  78. </div>
  79. <el-form-item label="站点名称" prop="siteName">
  80. <el-input
  81. v-model="form.siteName"
  82. placeholder="请输入站点名称"
  83. prefix-icon="el-icon-notebook-2"
  84. clearable
  85. />
  86. </el-form-item>
  87. </div>
  88. <!-- 投放配置 -->
  89. <div class="form-section">
  90. <div class="section-title">
  91. <i class="el-icon-setting"></i>
  92. <span>投放配置</span>
  93. </div>
  94. <el-form-item label="投放类型" prop="launchType">
  95. <el-select
  96. v-model="form.launchType"
  97. placeholder="请选择投放类型"
  98. style="width: 100%"
  99. prefix-icon="el-icon-s-flag"
  100. >
  101. <el-option
  102. v-for="item in launchTypeOptions"
  103. :key="item.value"
  104. :label="item.label"
  105. :value="item.value"
  106. />
  107. </el-select>
  108. </el-form-item>
  109. <el-form-item label="广告类型" prop="adType">
  110. <el-select
  111. v-model="form.adType"
  112. placeholder="请选择广告类型"
  113. style="width: 100%"
  114. >
  115. <el-option
  116. v-for="item in adTypeOptions"
  117. :key="item.value"
  118. :label="item.label"
  119. :value="item.value"
  120. />
  121. </el-select>
  122. </el-form-item>
  123. </div>
  124. <!-- 广告商信息 -->
  125. <div class="form-section">
  126. <div class="section-title">
  127. <i class="el-icon-office-building"></i>
  128. <span>广告商信息</span>
  129. </div>
  130. <el-form-item label="投放广告商" prop="advertiserId">
  131. <el-select
  132. v-model="form.advertiserId"
  133. placeholder="请选择投放广告商"
  134. style="width: 100%"
  135. @change="handleAdvertiserChange"
  136. filterable
  137. >
  138. <el-option
  139. v-for="item in advertiserList"
  140. :key="item.id"
  141. :label="item.advertiserName"
  142. :value="item.id"
  143. />
  144. </el-select>
  145. </el-form-item>
  146. <el-form-item label="投放广告商账号" prop="promotionAccountId">
  147. <el-select
  148. v-model="form.promotionAccountId"
  149. placeholder="请选择投放广告商账号"
  150. style="width: 100%"
  151. @change="handlePromotionAccountChange"
  152. filterable
  153. :disabled="!form.advertiserId"
  154. >
  155. <el-option
  156. v-for="item in promotionAccountList"
  157. :key="item.id"
  158. :label="item.accountName"
  159. :value="item.id"
  160. />
  161. </el-select>
  162. </el-form-item>
  163. </div>
  164. <!-- 页面和来源 -->
  165. <div class="form-section">
  166. <div class="section-title">
  167. <i class="el-icon-monitor"></i>
  168. <span>页面和来源</span>
  169. </div>
  170. <el-form-item label="投放页面" prop="launchPageId">
  171. <el-select
  172. v-model="form.launchPageId"
  173. placeholder="请选择投放页面"
  174. style="width: 100%"
  175. @change="handleLaunchPageChange"
  176. filterable
  177. >
  178. <el-option
  179. v-for="item in landingPageTemplateList"
  180. :key="item.id"
  181. :label="item.templateName"
  182. :value="item.id"
  183. />
  184. </el-select>
  185. </el-form-item>
  186. <el-form-item label="投放域名" prop="launchDomain">
  187. <el-select
  188. v-model="form.launchDomain"
  189. placeholder="请选择投放域名"
  190. style="width: 100%"
  191. filterable
  192. >
  193. <el-option
  194. v-for="item in launchDomainList"
  195. :key="item.id"
  196. :label="item.domain"
  197. :value="item.domain"
  198. />
  199. </el-select>
  200. </el-form-item>
  201. <el-form-item label="来源" prop="sourceId">
  202. <el-select
  203. v-model="form.sourceId"
  204. placeholder="请选择来源"
  205. style="width: 100%"
  206. @change="handleSourceChange"
  207. filterable
  208. >
  209. <el-option
  210. v-for="item in leadSourceList"
  211. :key="item.id"
  212. :label="item.sourceName"
  213. :value="item.id"
  214. />
  215. </el-select>
  216. </el-form-item>
  217. </div>
  218. <!-- 回传配置 -->
  219. <div class="form-section">
  220. <div class="section-title">
  221. <i class="el-icon-share"></i>
  222. <span>回传配置</span>
  223. </div>
  224. <el-form-item label="是否配置回传" prop="configCallback">
  225. <el-radio-group v-model="form.configCallback">
  226. <el-radio
  227. v-for="item in configCallbackOptions"
  228. :key="item.value"
  229. :label="item.value"
  230. :border="true"
  231. >{{ item.label }}</el-radio>
  232. </el-radio-group>
  233. </el-form-item>
  234. <el-form-item
  235. v-if="form.configCallback === 1"
  236. label="回传账号"
  237. prop="callbackAccountId"
  238. class="slide-fade"
  239. >
  240. <el-select
  241. v-model="form.callbackAccountId"
  242. placeholder="请选择回传账号"
  243. style="width: 100%"
  244. @change="handleCallbackAccountChange"
  245. filterable
  246. >
  247. <el-option
  248. v-for="item in callbackAccountList"
  249. :key="item.id"
  250. :label="item.callbackAccountName"
  251. :value="item.id"
  252. />
  253. </el-select>
  254. </el-form-item>
  255. </div>
  256. </el-form>
  257. <div slot="footer" class="dialog-footer">
  258. <el-button @click="cancel">取 消</el-button>
  259. <el-button type="primary" @click="submitForm" icon="el-icon-check">确 定</el-button>
  260. </div>
  261. </el-dialog>
  262. <!-- 站点统计对话框 -->
  263. <el-dialog title="站点统计" :visible.sync="statisticsOpen" width="800px" append-to-body>
  264. <el-descriptions :column="2" border v-if="statistics">
  265. <el-descriptions-item label="站点ID">{{ statistics.siteId }}</el-descriptions-item>
  266. <el-descriptions-item label="访问量">{{ statistics.visitCount || 0 }}</el-descriptions-item>
  267. <el-descriptions-item label="访客数">{{ statistics.visitorCount || 0 }}</el-descriptions-item>
  268. <el-descriptions-item label="转化数">{{ statistics.conversionCount || 0 }}</el-descriptions-item>
  269. <el-descriptions-item label="转化率">
  270. {{ statistics.conversionRate ? (statistics.conversionRate * 100).toFixed(2) + '%' : '0%' }}
  271. </el-descriptions-item>
  272. <el-descriptions-item label="统计时间">{{ parseTime(statistics.updateTime) }}</el-descriptions-item>
  273. </el-descriptions>
  274. <div v-else style="text-align: center; padding: 40px 0;">
  275. <el-empty description="暂无统计数据"></el-empty>
  276. </div>
  277. <div slot="footer" class="dialog-footer">
  278. <el-button @click="statisticsOpen = false">关 闭</el-button>
  279. </div>
  280. </el-dialog>
  281. </div>
  282. </template>
  283. <script>
  284. import { listSite, getSite, addSite, updateSite, delSite, getSiteStatistics } from "@/api/adv/site";
  285. import { pageAdvertiser } from "@/api/adv/advertiser";
  286. import { pagePromotionAccount } from "@/api/adv/promotionAccount";
  287. import { pageTemplate } from "@/api/adv/landingPageTemplate";
  288. import { pageLeadSource } from "@/api/adv/leadSource";
  289. import { pageCallbackAccount } from "@/api/adv/callbackAccount";
  290. import { pageDomain } from "@/api/adv/domain";
  291. export default {
  292. name: "Site",
  293. data() {
  294. return {
  295. // 遮罩层
  296. loading: true,
  297. // 站点表格数据
  298. siteList: [],
  299. // 弹出层标题
  300. title: "",
  301. // 是否显示弹出层
  302. open: false,
  303. // 是否显示统计弹出层
  304. statisticsOpen: false,
  305. // 统计数据
  306. statistics: null,
  307. // 表单参数
  308. form: {},
  309. // 投放类型选项
  310. launchTypeOptions: [
  311. { label: "线上投放", value: "线上投放" },
  312. { label: "线下投放", value: "线下投放" }
  313. ],
  314. // 广告类型选项
  315. adTypeOptions: [
  316. { label: "信息流广告", value: "信息流广告" }
  317. ],
  318. // 是否配置回传选项
  319. configCallbackOptions: [
  320. { label: "否", value: 0 },
  321. { label: "是", value: 1 }
  322. ],
  323. // 广告商列表
  324. advertiserList: [],
  325. // 广告商账号列表
  326. promotionAccountList: [],
  327. // 落地页模板列表
  328. landingPageTemplateList: [],
  329. // 线索来源列表
  330. leadSourceList: [],
  331. // 回传账号列表
  332. callbackAccountList: [],
  333. // 投放域名列表
  334. launchDomainList: [],
  335. // 表单校验
  336. rules: {
  337. siteName: [
  338. { required: true, message: "站点名称不能为空", trigger: "blur" }
  339. ],
  340. siteUrl: [
  341. { required: true, message: "站点地址不能为空", trigger: "blur" }
  342. ],
  343. launchType: [
  344. { required: true, message: "请选择投放类型", trigger: "change" }
  345. ],
  346. adType: [
  347. { required: true, message: "请选择广告类型", trigger: "change" }
  348. ],
  349. advertiserId: [
  350. { required: true, message: "请选择投放广告商", trigger: "change" }
  351. ],
  352. promotionAccountId: [
  353. { required: true, message: "请选择投放广告商账号", trigger: "change" }
  354. ],
  355. launchPageId: [
  356. { required: true, message: "请选择投放页面", trigger: "change" }
  357. ],
  358. sourceId: [
  359. { required: true, message: "请选择来源", trigger: "change" }
  360. ],
  361. launchDomain: [
  362. { required: true, message: "请选择投放域名", trigger: "change" }
  363. ],
  364. configCallback: [
  365. { required: true, message: "请选择是否配置回传", trigger: "change" }
  366. ],
  367. callbackAccountId: [
  368. {
  369. validator: (rule, value, callback) => {
  370. if (this.form.configCallback === 1 && !value) {
  371. callback(new Error('请选择回传账号'));
  372. } else {
  373. callback();
  374. }
  375. },
  376. trigger: "change"
  377. }
  378. ]
  379. }
  380. };
  381. },
  382. created() {
  383. this.getList();
  384. },
  385. methods: {
  386. /** 查询站点列表 */
  387. getList() {
  388. this.loading = true;
  389. listSite().then(response => {
  390. this.siteList = response.data;
  391. this.loading = false;
  392. });
  393. },
  394. // 取消按钮
  395. cancel() {
  396. this.open = false;
  397. this.reset();
  398. },
  399. // 表单重置
  400. reset() {
  401. this.form = {
  402. id: undefined,
  403. siteName: undefined,
  404. siteUrl: undefined,
  405. launchType: undefined,
  406. adType: undefined,
  407. advertiserId: undefined,
  408. advertiserName: undefined,
  409. promotionAccountId: undefined,
  410. promotionAccountName: undefined,
  411. launchPageId: undefined,
  412. launchPageName: undefined,
  413. sourceId: undefined,
  414. sourceName: undefined,
  415. launchDomain: undefined,
  416. configCallback: 0,
  417. callbackAccountId: undefined,
  418. callbackAccountName: undefined
  419. };
  420. this.promotionAccountList = [];
  421. this.launchDomainList = [];
  422. this.resetForm("form");
  423. },
  424. /** 新增按钮操作 */
  425. handleAdd() {
  426. this.reset();
  427. this.loadSelectOptions();
  428. this.open = true;
  429. this.title = "添加站点";
  430. },
  431. /** 修改按钮操作 */
  432. handleUpdate(row) {
  433. this.reset();
  434. this.loadSelectOptions();
  435. getSite(row.id).then(response => {
  436. this.form = response.data;
  437. // 如果有广告商ID,加载对应的广告商账号列表
  438. if (this.form.advertiserId) {
  439. this.loadPromotionAccountList(this.form.advertiserId);
  440. }
  441. this.open = true;
  442. this.title = "修改站点";
  443. });
  444. },
  445. /** 查看统计按钮操作 */
  446. handleStatistics(row) {
  447. this.statistics = null;
  448. this.statisticsOpen = true;
  449. getSiteStatistics(row.id).then(response => {
  450. this.statistics = response.data;
  451. }).catch(() => {
  452. this.statistics = null;
  453. });
  454. },
  455. /** 提交按钮 */
  456. submitForm() {
  457. this.$refs["form"].validate(valid => {
  458. if (valid) {
  459. if (this.form.id != undefined) {
  460. updateSite(this.form.id, this.form).then(response => {
  461. if (response.code === 200) {
  462. this.msgSuccess("修改成功");
  463. this.open = false;
  464. this.getList();
  465. }
  466. });
  467. } else {
  468. addSite(this.form).then(response => {
  469. if (response.code === 200) {
  470. this.msgSuccess("新增成功");
  471. this.open = false;
  472. this.getList();
  473. }
  474. });
  475. }
  476. }
  477. });
  478. },
  479. /** 删除按钮操作 */
  480. handleDelete(row) {
  481. this.$confirm('是否确认删除站点"' + row.siteName + '"?', "警告", {
  482. confirmButtonText: "确定",
  483. cancelButtonText: "取消",
  484. type: "warning"
  485. }).then(() => {
  486. return delSite(row.id);
  487. }).then(() => {
  488. this.getList();
  489. this.msgSuccess("删除成功");
  490. }).catch(function() {});
  491. },
  492. /** 加载下拉选项数据 */
  493. loadSelectOptions() {
  494. // 加载广告商列表
  495. pageAdvertiser({ pageNum: 1, pageSize: 1000 }).then(response => {
  496. this.advertiserList = response.data.records;
  497. }).catch(error => {
  498. console.error('加载广告商列表失败:', error);
  499. this.advertiserList = [];
  500. });
  501. // 加载落地页模板列表
  502. pageTemplate({ pageNum: 1, pageSize: 1000, status: 1 }).then(response => {
  503. this.landingPageTemplateList = response.data.records;
  504. }).catch(error => {
  505. console.error('加载落地页模板失败:', error);
  506. this.landingPageTemplateList = [];
  507. });
  508. // 加载线索来源列表
  509. pageLeadSource({ pageNum: 1, pageSize: 1000 }).then(response => {
  510. this.leadSourceList = response.data.records;
  511. }).catch(error => {
  512. console.error('加载线索来源失败:', error);
  513. this.leadSourceList = [];
  514. });
  515. // 加载回传账号列表
  516. pageCallbackAccount({ pageNum: 1, pageSize: 1000 }).then(response => {
  517. this.callbackAccountList = response.data.records;
  518. }).catch(error => {
  519. console.error('加载回传账号失败:', error);
  520. this.callbackAccountList = [];
  521. });
  522. // 加载域名列表
  523. pageDomain({ pageNum: 1, pageSize: 1000, status: 1 }).then(response => {
  524. this.launchDomainList = response.data.records;
  525. }).catch(error => {
  526. console.error('加载域名失败:', error);
  527. this.launchDomainList = [];
  528. });
  529. },
  530. /** 广告商变化时 */
  531. handleAdvertiserChange(advertiserId) {
  532. this.form.advertiserName = "";
  533. this.form.promotionAccountId = undefined;
  534. this.form.promotionAccountName = "";
  535. this.promotionAccountList = [];
  536. if (advertiserId) {
  537. const advertiser = this.advertiserList.find(item => item.id === advertiserId);
  538. if (advertiser) {
  539. this.form.advertiserName = advertiser.advertiserName;
  540. }
  541. // 加载对应的广告商账号列表
  542. this.loadPromotionAccountList(advertiserId);
  543. }
  544. },
  545. /** 加载广告商账号列表 */
  546. loadPromotionAccountList(advertiserId) {
  547. if (!advertiserId) return;
  548. pagePromotionAccount({ pageNum: 1, pageSize: 1000, advertiserId: advertiserId }).then(response => {
  549. this.promotionAccountList = response.data.records;
  550. }).catch(error => {
  551. console.error('加载广告商账号失败:', error);
  552. this.promotionAccountList = [];
  553. });
  554. },
  555. /** 广告商账号变化时 */
  556. handlePromotionAccountChange(promotionAccountId) {
  557. this.form.promotionAccountName = "";
  558. if (promotionAccountId) {
  559. const account = this.promotionAccountList.find(item => item.id === promotionAccountId);
  560. if (account) {
  561. this.form.promotionAccountName = account.accountName;
  562. }
  563. }
  564. },
  565. /** 投放页面变化时 */
  566. handleLaunchPageChange(launchPageId) {
  567. this.form.launchPageName = "";
  568. if (launchPageId) {
  569. const template = this.landingPageTemplateList.find(item => item.id === launchPageId);
  570. if (template) {
  571. this.form.launchPageName = template.templateName;
  572. }
  573. }
  574. },
  575. /** 线索来源变化时 */
  576. handleSourceChange(sourceId) {
  577. this.form.sourceName = "";
  578. if (sourceId) {
  579. const source = this.leadSourceList.find(item => item.id === sourceId);
  580. if (source) {
  581. this.form.sourceName = source.sourceName;
  582. }
  583. }
  584. },
  585. /** 回传账号变化时 */
  586. handleCallbackAccountChange(callbackAccountId) {
  587. this.form.callbackAccountName = "";
  588. if (callbackAccountId) {
  589. const account = this.callbackAccountList.find(item => item.id === callbackAccountId);
  590. if (account) {
  591. this.form.callbackAccountName = account.callbackAccountName;
  592. }
  593. }
  594. }
  595. }
  596. };
  597. </script>
  598. <style lang="scss" scoped>
  599. .app-container {
  600. padding: 20px;
  601. }
  602. // 对话框样式
  603. ::v-deep .site-dialog {
  604. .el-dialog__header {
  605. background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  606. padding: 20px;
  607. margin: 0;
  608. border-radius: 4px 4px 0 0;
  609. .el-dialog__title {
  610. color: #fff;
  611. font-size: 18px;
  612. font-weight: 500;
  613. }
  614. .el-dialog__headerbtn .el-dialog__close {
  615. color: #fff;
  616. font-size: 20px;
  617. &:hover {
  618. color: #f0f0f0;
  619. }
  620. }
  621. }
  622. .el-dialog__body {
  623. padding: 25px 30px;
  624. background-color: #f8f9fa;
  625. }
  626. .el-dialog__footer {
  627. padding: 15px 30px;
  628. border-top: 1px solid #e8e8e8;
  629. background-color: #fff;
  630. }
  631. }
  632. // 表单样式
  633. .elegant-form {
  634. .form-section {
  635. background: #fff;
  636. border-radius: 8px;
  637. padding: 20px;
  638. margin-bottom: 20px;
  639. box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
  640. transition: all 0.3s ease;
  641. &:hover {
  642. box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
  643. transform: translateY(-2px);
  644. }
  645. &:last-child {
  646. margin-bottom: 0;
  647. }
  648. }
  649. .section-title {
  650. display: flex;
  651. align-items: center;
  652. margin-bottom: 20px;
  653. padding-bottom: 12px;
  654. border-bottom: 2px solid #e8e8e8;
  655. font-size: 15px;
  656. font-weight: 600;
  657. color: #303133;
  658. i {
  659. font-size: 18px;
  660. margin-right: 8px;
  661. background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  662. -webkit-background-clip: text;
  663. -webkit-text-fill-color: transparent;
  664. background-clip: text;
  665. }
  666. span {
  667. position: relative;
  668. &::after {
  669. content: '';
  670. position: absolute;
  671. left: 0;
  672. bottom: -12px;
  673. width: 0;
  674. height: 2px;
  675. background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  676. transition: width 0.3s ease;
  677. }
  678. }
  679. }
  680. .form-section:hover .section-title span::after {
  681. width: 100%;
  682. }
  683. .el-form-item {
  684. margin-bottom: 20px;
  685. &:last-child {
  686. margin-bottom: 0;
  687. }
  688. }
  689. ::v-deep .el-form-item__label {
  690. color: #606266;
  691. font-weight: 500;
  692. font-size: 14px;
  693. }
  694. ::v-deep .el-input__inner,
  695. ::v-deep .el-textarea__inner {
  696. border-radius: 6px;
  697. border: 1px solid #dcdfe6;
  698. transition: all 0.3s ease;
  699. &:hover {
  700. border-color: #c0c4cc;
  701. }
  702. &:focus {
  703. border-color: #667eea;
  704. box-shadow: 0 0 0 2px rgba(102, 126, 234, 0.1);
  705. }
  706. }
  707. ::v-deep .el-select {
  708. .el-input__inner {
  709. padding-left: 15px;
  710. }
  711. }
  712. ::v-deep .el-radio {
  713. margin-right: 20px;
  714. &.is-bordered {
  715. border-radius: 6px;
  716. padding: 10px 20px;
  717. transition: all 0.3s ease;
  718. &:hover {
  719. border-color: #667eea;
  720. }
  721. &.is-checked {
  722. border-color: #667eea;
  723. background-color: rgba(102, 126, 234, 0.05);
  724. }
  725. }
  726. }
  727. // 回传账号显示动画
  728. .slide-fade {
  729. animation: slideDown 0.3s ease;
  730. }
  731. }
  732. @keyframes slideDown {
  733. from {
  734. opacity: 0;
  735. transform: translateY(-10px);
  736. }
  737. to {
  738. opacity: 1;
  739. transform: translateY(0);
  740. }
  741. }
  742. // 按钮样式优化
  743. .dialog-footer {
  744. text-align: right;
  745. .el-button {
  746. padding: 10px 24px;
  747. border-radius: 6px;
  748. font-weight: 500;
  749. transition: all 0.3s ease;
  750. &.el-button--primary {
  751. background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  752. border: none;
  753. &:hover {
  754. transform: translateY(-2px);
  755. box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
  756. }
  757. &:active {
  758. transform: translateY(0);
  759. }
  760. }
  761. &.el-button--default {
  762. &:hover {
  763. color: #667eea;
  764. border-color: #667eea;
  765. background-color: rgba(102, 126, 234, 0.05);
  766. }
  767. }
  768. }
  769. }
  770. // 表格样式优化
  771. .el-table {
  772. border-radius: 8px;
  773. overflow: hidden;
  774. box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
  775. ::v-deep .el-table__header {
  776. th {
  777. background-color: #f5f7fa;
  778. color: #606266;
  779. font-weight: 600;
  780. font-size: 14px;
  781. }
  782. }
  783. ::v-deep .el-table__row {
  784. transition: all 0.3s ease;
  785. &:hover {
  786. background-color: rgba(102, 126, 234, 0.05);
  787. }
  788. }
  789. }
  790. // 顶部按钮组样式
  791. .mb8 {
  792. margin-bottom: 15px;
  793. .el-button {
  794. border-radius: 6px;
  795. padding: 10px 20px;
  796. font-weight: 500;
  797. transition: all 0.3s ease;
  798. &.el-button--primary {
  799. background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  800. border: none;
  801. &:hover {
  802. transform: translateY(-2px);
  803. box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
  804. }
  805. }
  806. &.el-button--success {
  807. &:hover {
  808. transform: translateY(-2px);
  809. box-shadow: 0 4px 12px rgba(103, 194, 58, 0.4);
  810. }
  811. }
  812. }
  813. }
  814. </style>