当前位置: 首页 > news >正文

企业网站轮播图怎么做360竞价推广客服电话

企业网站轮播图怎么做,360竞价推广客服电话,手机网站开发设计,安徽网站建设费用前言 hallo,又是许久未见,昨天也是正式把公司内部的广告投放平台暂时落地,我也即将离开待了两年多的地方。言归正传,由于头条广告后台的升级改版,因此为了满足内部投放需求,做了一个可视化的投放平台&…

前言

hallo,又是许久未见,昨天也是正式把公司内部的广告投放平台暂时落地,我也即将离开待了两年多的地方。言归正传,由于头条广告后台的升级改版,因此为了满足内部投放需求,做了一个可视化的投放平台,时间大概是1个星期多,然后我会阐述我是如何在最短的时间内完成这个平台的。

需求梳理

  • 时间尽可能的短
    为了实现这一点,可能暂时不考虑非常华丽的UI设计和变化,对于后台系统来说,快速上手和简单易用是最重要的

  • 技术选型
    实际上在头条后台没有改版之前,公司内部使用的是React18那一套玩法,我也亲身参与和维护过,那么之所以选择vue3。
    一方面是:vue3发展到现在跟React的差距并不是特别大,特别在复杂的计算场景上面。
    二方面是:兼顾公司内部前端的技术栈考虑,公司主要以vue为主,那么为了之后的维护和更新,vue技术栈最合适不过
    三方面是:旧版广告系统迭代了非常多版本,既不满足新版需求,其次是数据维护极其困难,数据污染严重,规范和可靠性不强,因此二次开发比重新创作难度更大。

  • 调试简单

  • 易于维护

设计思路

借鉴于上一代广告投放系统的痛点,其实最难的就是数据的回显和数据的汇总,导致出现的各种问题。因此我给自己提出以下几点原则:

  • 不违背单向数据源原则
    数据最终的处理,应该由顶层决定,数据可以获取,但是修改必须经过顶层同意。
    在这里插入图片描述

  • 数据驱动
    为了解决回显问题,实际上迭代到后期,组件和组件之间嵌套的层级可能会比较深,那么就需要一个公共数据管理库去统一管理数据,所有的数据在初始化的时候,可以通过接口获取数据之后,存储到数据管理库去统一处理,再在各个组件中做监听,数据变化驱动视图变化。

在这里插入图片描述

方案选型

  1. 公共状态管理库(pinia)官方支持,社区活跃
  2. 请求库(axios),请求响应拦截,二次封装,自定义参数
  3. 打包构建工具(vite),本地代理,插件丰富,官方支持
  4. 组件库(element-plus),支持企业级系统搭建,适配vue3
  5. 路由管理(vue-router)
  6. 调试工具(code-inspector-plugin)支持多种前端技术框架和构建工具,ide支持,当组件嵌套深层次的时候,能通过快捷键快速定位代码位置

项目搭建

那么初次创建项目的时候,所有的资源都会在src下面去创作,结构大致如下,我也借鉴了在Flutter端和在React端的经验。
在这里插入图片描述

目录解析

  • 所有的请求都在api目录下面,不同的请求api类型区分不同的文件,比如账户相关单独一个文件,资源包相关单独一个文件
  • 静态资源都在assets下面
  • 所有的组件都放在components下面,不同的组件类型单独一个文件夹命名,子组件也是如此
  • 路由文件放在router下面,统一由index.js去对外暴露
  • 公共管理库放在store下面,统一由index.js去对外暴露
  • 所有的工具类放在utils,不同种类的工具单独文件夹分开处理
  • 主视图文件放在views下面,充当为顶层决策者
  • App.vue文件为最高决策者,如果未来规划页面布局应当在此规划

本地代理

vite本身就支持本地代理操作,所有的配置都在vite.config.js文件中去配置,假如是公司内部使用,那么公司内部系统会在请求上带上特殊参数,那么可以通过代理的手段去设置。

 server: {// port: 8080, // 设置端口号// host: '192.168.11.5',proxy: {'/static': {target: 'http://xxxxxx.com',ws: false,changeOrigin: true,rewrite: (path) => path.replace(/^\/static/, ''),configure: (proxy) => {proxy.on('proxyReq', function (proxyReq) {proxyReq.setHeader('Cookie', 'my_dev=a221cc9410bae508aa3c1106af467db5; ly_my_user=Ikft5xHzizNCSKdfddTxTVyj9wZ3pdhh; ly_my_refer=1380.8.74.2; PHPSESSID=fufvppacre3nor48aos834dtei; SERVERID=6c7b09313256c7aae1d2b6b27bf60e38|1732675765|1732675765');});}},

请求配置

本地代理是需要和请求配置相对应的,当处理本地环境时,请求链接为配置好的代理头,触发本地代理,完成本地请求。

const service = axios.create({timeout: 5000,timeoutRetry: true,// 请求头信息headers: {'Content-Type': 'application/x-www-form-urlencoded'},// 携带凭证withCredentials: true,// 返回数据类型// responseType: 'json'
})service.interceptors.request.use(config => {// 根据custom参数中配置的是否需要baseURL,添加对应的请求头if (config?.custom?.baseUrlType) {if (process.env.NODE_ENV === 'development') {config.baseURL = `/${config.custom.baseUrlType}`} else {config.baseURL = `//${config.custom.baseUrlType}.${window.location.host.split('.')[1]}.com`}}

我在请求拦截中就是这么做的。

打包配置

打包注意要在vite.config.js中进行配置,否则可能出现找不到资源的问题

  base: process.env.NODE_ENV === 'production' ? './' : '/',

资源访问配置

这里我为了方便资源的获取,将src目录设置别名

  resolve: {alias: {'@': path.resolve(__dirname, 'src')}},

难点和解析

数据传递

前面说过了,我希望做到的就是不违背单一数据源的原则,因此在数据的处理上,我希望最终都交给顶层去决策,那么是这么处理的:

对于子级组件,我在父级中给予一个ref,为了能够操作子组件

  <CustomTemplate name="选择账户"><Account ref="AccountRef" /></CustomTemplate>const AccountRef = ref();

子组件对外暴露一个handleSubmit方法,用于对外抛出数据

const account = await AccountRef.value.handleSubmit() 

这样有几个好处:

  1. 组件和组件之前是解耦的。
    不会说一个组件影响到了其他组件,组件的事情组件自己处理,我要的只是处理完之后的数据,在多人开发中,可以把页面组件化,这一块组件只做自己的事情,最终要的只是通过你暴露出来的方法拿到最终处理好的数据即可。
  2. 单一数据源原则

地域数据树级处理

从接口拿到中国地域数据的时候,首先要考虑以下问题:

  • 数据量大
    地域数据通常数据量都会比较大,反复的调用接口去拿显然是不合适的,因为地域数据通常是不会改变的,因此在首次调用接口成功拿到数据之后,就应该存储本地了,之后都看本地有就本地拿,没有就调用接口,接口需要更新再重新调用重新执行流程
  • 怎么存
    对于浏览器来说,存储的方式有很多种:
    例如cookie、LocalStorage、SessionStorage、浏览器数据库。
    但是我们的数据量比较大,可能需要反复读写,那么就要求:
    一是能够存储比较大的数据
    二是读写得快,不能阻塞渲染进程
    那么数据库IndexedDB就非常合适了,存储容量大,异步操作不阻塞线程,数据结构也足够灵活
  • 怎么展示
    展示当然就选中了element-plus的树级选择器了,但是需要注意的是,由于地域在嵌套方面比较深的时候,渲染会慢,因此我们需要做懒加载(render-after-expand),并且选中的时候,不能选中父子级联(check-strictly),否则严重阻塞渲染。
  <el-tree-select v-model="ACForm.city" :data="cityOptions" multiple :render-after-expand="true"show-checkbox style="width: 240px" placeholder="请选择区域" node-key="id" check-strictly />

并且大概率从头条获取的数据并不是一个标准的树级结构,因此需要修改为标准的树级结构

const addAdminSelectorsCity = (data) => {let arr = [];data.map(item => {let newData = {};//这一块根据选用的组件来newData.label = item.name; newData.id = item.geoname_id;newData.value = item.code;if (item.name === "台湾省" ||item.name === "香港特别行政区" ||item.name === "澳门特别行政区") {newData.value = item.geoname_id;}if (item.sub_districts) {newData.children = addAdminSelectorsCity(item.sub_districts);if (newData.label === newData.children[0].label) {newData = newData.children[0];}}return arr.push(newData);});return arr;
};

可动态增删数据的数据校验

本身其实el-form 配合el-form-item是有一套标准的校验规范的,在非动态的数据校验不会有任何问题。

 <el-form :model="AdBudgetForm" :rules="rules" style="max-width: 600px" label-width="auto" label-position="left"size="default" ref="AdBudgetRef"><div class="form-container"><el-form-item label="广告预算" prop="budget"><el-input v-model="AdBudgetForm.budget" style="width: 240px" placeholder="请输入广告预算"><template #suffix>元</template></el-input></el-form-item></div></el-form>

表单定义一个AdBudgetForm负责获取所有数据,定义一个rules负责处理规则,每一项都使用el-form-item进行包裹,通过设置prop去匹配rules中的校验,去完成校验。

但是有一种场景:
在这里插入图片描述
点击能够不断添加,删除又可以批量删除和添加,当条数比较多的时候,肯定不能去手动定义非常多的rules去处理,因为他们实际上校验规则都是同一个。

只需要这么处理即可:

            <div style="margin-bottom: 10px;" v-for="(item, index) in textSourceForm.textSourceList" :key="index"><el-form-item :prop="'textSourceList.' + index + '.title'" :rules="rules.title">const rules = reactive({title: [{validator: textRules,trigger: 'blur'}]
});

头条自定义日期选择组件

这个组件是在这一位开源的基础上进行二次改造成vue3版本的,完整实例如下,开箱即用:
开源作者地址:https://github.com/xiejunping/andt-components
在这里插入图片描述

        <TimeRangePicker ref="TimeRangePickerRef" :value="mult_timeRange" :data="weektimeDataArr"@clearSelection="clearSelection" />const weektimeDataArr = ref(weektimeData)
const mult_timeRange = computed(() => {  //时间范围return weektimeDataArr.value.map((item) => {return {id: item.row,week: item.value,value: splicing(item.child),};});
});const clearSelection = () => {weektimeDataArr.value.forEach((item) => {item.child.forEach((t) => {t.check = false; // 直接修改属性即可});});
}

weektimeData

const formatDate = (date, fmt) => {const o = {'M+': date.getMonth() + 1,'d+': date.getDate(),'h+': date.getHours(),'m+': date.getMinutes(),'s+': date.getSeconds(),'q+': Math.floor((date.getMonth() + 3) / 3),S: date.getMilliseconds()}if (/(y+)/.test(fmt)) {fmt = fmt.replace(RegExp.$1,(date.getFullYear() + '').substr(4 - RegExp.$1.length))}for (const k in o) {if (new RegExp('(' + k + ')').test(fmt)) {fmt = fmt.replace(RegExp.$1,RegExp.$1.length === 1 ? o[k] : ('00' + o[k]).substr(('' + o[k]).length))}}return fmt
}const createArr = (len) => {return Array.from(Array(len)).map((ret, id) => id)
}const formatWeektime = (col) => {const timestamp = 1542384000000 // '2018-11-17 00:00:00'const beginstamp = timestamp + col * 1800000 // col * 30 * 60 * 1000const endstamp = beginstamp + 1800000const begin = formatDate(new Date(beginstamp), 'hh:mm')const end = formatDate(new Date(endstamp), 'hh:mm')return `${begin}~${end}`
}const data = ['星期一','星期二','星期三','星期四','星期五','星期六','星期日'
].map((ret, index) => {const children = (ret, row, max) => {return createArr(max).map((t, col) => {return {week: ret,value: formatWeektime(col),begin: formatWeektime(col).split('~')[0],end: formatWeektime(col).split('~')[1],row,col}})}return {value: ret,row: index,child: children(ret, index, 48)}
})export default data
<template><div class="c-weektime"><div class="c-schedue"></div><div :class="{ 'c-schedue': true, 'c-schedue-notransi': mode }" :style="styleValue"></div><table :class="{ 'c-min-table': colspan < 2 }" class="c-weektime-table"><thead class="c-weektime-head"><tr><th rowspan="8" class="week-td">星期/时间</th><th :colspan="12 * colspan">00:00 - 12:00</th><th :colspan="12 * colspan">12:00 - 24:00</th></tr><tr><td v-for="t in theadArr" :key="t" :colspan="colspan">{{ t }}</td></tr></thead><tbody class="c-weektime-body"><tr v-for="t in data" :key="t.row"><td>{{ t.value }}</td><td v-for="n in t.child" :key="`${n.row}-${n.col}`" :data-week="n.row" :data-time="n.col":class="selectClasses(n)" @mouseenter="cellEnter(n)" @mousedown="cellDown(n)"@mouseup="cellUp(n)" class="weektime-atom-item"></td></tr><tr><td colspan="49" class="c-weektime-preview"><div class="g-clearfix c-weektime-con"><span class="g-pull-left">{{selectState ? '已选择时间段' : '可拖动鼠标选择时间段'}}</span><a @click.prevent="clearSelection" class="g-pull-right">清空选择</a></div><div v-if="selectState" class="c-weektime-time"><div v-for="t in selectValue" :key="t.id"><p v-if="t.value"><span class="g-tip-text">{{ t.week }}</span><span>{{ t.value }}</span></p></div></div></td></tr></tbody></table></div>
</template><script setup>
import { computed, defineEmits, defineExpose, defineProps, ref } from "vue";const emit = defineEmits(['clearSelection']);// Props
const props = defineProps({value: { type: Array, required: true },data: { type: Array, required: true },colspan: { type: Number, default: 2 },
});// Reactive State
const width = ref(0);
const height = ref(0);
const left = ref(0);
const top = ref(0);
const mode = ref(0);
const row = ref(0);
const col = ref(0);
const theadArr = ref([]);// Helper Function
const createArr = (len) => Array.from({ length: len }, (_, id) => id);// Computed Properties
const styleValue = computed(() => ({width: `${width.value}px`,height: `${height.value}px`,left: `${left.value}px`,top: `${top.value}px`,
}));const selectValue = computed(() => props.value);const selectState = computed(() => props.value.some((ret) => ret.value));const selectClasses = (n) => (n.check ? "ui-selected" : "");// Initialization (created hook)
theadArr.value = createArr(24);// Methods
const cellEnter = (item) => {const ele = document.querySelector(`td[data-week='${item.row}'][data-time='${item.col}']`);if (!ele) return;if (!mode.value) {left.value = ele.offsetLeft;top.value = ele.offsetTop;} else if (item.col <= col.value && item.row <= row.value) {width.value = (col.value - item.col + 1) * ele.offsetWidth;height.value = (row.value - item.row + 1) * ele.offsetHeight;left.value = ele.offsetLeft;top.value = ele.offsetTop;} else if (item.col >= col.value && item.row >= row.value) {width.value = (item.col - col.value + 1) * ele.offsetWidth;height.value = (item.row - row.value + 1) * ele.offsetHeight;if (item.col > col.value && item.row === row.value) top.value = ele.offsetTop;if (item.col === col.value && item.row > row.value) left.value = ele.offsetLeft;} else if (item.col > col.value && item.row < row.value) {width.value = (item.col - col.value + 1) * ele.offsetWidth;height.value = (row.value - item.row + 1) * ele.offsetHeight;top.value = ele.offsetTop;} else if (item.col < col.value && item.row > row.value) {width.value = (col.value - item.col + 1) * ele.offsetWidth;height.value = (item.row - row.value + 1) * ele.offsetHeight;left.value = ele.offsetLeft;}
};const cellDown = (item) => {const ele = document.querySelector(`td[data-week='${item.row}'][data-time='${item.col}']`);if (!ele) return;mode.value = 1;width.value = ele.offsetWidth;height.value = ele.offsetHeight;row.value = item.row;col.value = item.col;
};const cellUp = (item) => {if (item.col <= col.value && item.row <= row.value) {selectWeek([item.row, row.value], [item.col, col.value], !item.check);} else if (item.col >= col.value && item.row >= row.value) {selectWeek([row.value, item.row], [col.value, item.col], !item.check);} else if (item.col > col.value && item.row < row.value) {selectWeek([item.row, row.value], [col.value, item.col], !item.check);} else if (item.col < col.value && item.row > row.value) {selectWeek([row.value, item.row], [item.col, col.value], !item.check);}width.value = 0;height.value = 0;mode.value = 0;
};const selectWeek = (rowRange, colRange, check) => {const [minRow, maxRow] = rowRange;const [minCol, maxCol] = colRange;props.data.forEach((item) => {item.child.forEach((t) => {if (t.row >= minRow &&t.row <= maxRow &&t.col >= minCol &&t.col <= maxCol) {t.check = check;}});});
};const clearSelection = () => {emit('clearSelection')
};defineExpose({selectValue
})
</script><style lang="scss" scoped>
.c-weektime {min-width: 640px;position: relative;display: inline-block;
}.c-schedue {background: #598fe6;position: absolute;width: 0;height: 0;opacity: 0.6;pointer-events: none;&-notransi {transition: width 0.12s ease, height 0.12s ease, top 0.12s ease,left 0.12s ease;}
}.c-weektime-table {border-collapse: collapse;th {vertical-align: inherit;font-weight: bold;}tr {height: 30px;}tr,td,th {user-select: none;border: 1px solid #dee4f5;text-align: center;line-height: 1.0em;transition: background 0.2s ease;}.c-weektime-head {font-size: 12px;.week-td {width: 9.2em !important;}}.c-weektime-body {font-size: 12px;td {&.weektime-atom-item {user-select: unset;background-color: #f5f5f5;width: 1.4em;}&.ui-selected {background-color: #598fe6;}}}.c-weektime-preview {line-height: 2.4em;padding: 0 10px;font-size: 14px;.c-weektime-con {line-height: 46px;user-select: none;}.c-weektime-time {text-align: left;line-height: 2.4em;p {max-width: 625px;line-height: 1.4em;word-break: break-all;margin-bottom: 8px;}}}
}.c-min-table {tr,td,th {min-width: 24px;}
}.g-clearfix {&:after,&:before {clear: both;content: ' ';display: table;}
}.g-pull-left {float: left;
}.g-pull-right {float: right;
}.g-tip-text {color: #999;
}
</style>

性能优化和迭代

  • 在点击不同操作的时候,注意浏览器的性能指标,及时发现究竟哪些操作会阻塞到渲染进程
  • 目前仅是单账户和单广告,实际上做成多账户和多广告只需要补充一些匹配规则,然后做不同的分配即可。

总结

本文简单阐述使用vue3去实现简易版的广告投放系统中遇到的问题和解决思路,希望能给到一些设计思路,同时如果有更好的建议,欢迎提出!

http://www.ds6.com.cn/news/1147.html

相关文章:

  • 网站建设销售话术开场白百度我的订单查询
  • 网站封面怎么做品牌推广策略分析
  • 镇海淘宝网站建设如何搭建一个自己的网站
  • redis做网站自建网站
  • 房地产楼盘微信网站建设营销方案googleseo服务公司
  • 多用户商城网站建设方案市场营销策划方案案例
  • wordpress模板企业泉州seo按天计费
  • 怎么建优惠券网站百度商城官网首页
  • 网站建设最新技术网页设计需要学什么软件
  • 广州app制作黄山seo
  • 电商类网站开发网络公司起名
  • 免费电视剧在线观看网站企业百度推广
  • 用html建设网站创建网页
  • 百度网盘如何获得2t免费空间台州百度快照优化公司
  • 营销型网站平台建设免费永久个人域名注册
  • 如何在网站后台做超链接到文档哈尔滨网络推广优化
  • 如何做网站不被坑大型门户网站建设
  • 阿里巴巴网站上面产品描述一般怎么做的网站是怎么做出来的
  • 资料网站怎么做企业门户网站
  • wordpress 主题 日本成都网站seo性价比高
  • 定制研发服务企业网站推广优化
  • 匿名聊天网站怎么做各大搜索引擎提交入口
  • 平台网站建设方案标书产品设计
  • 个人做的好的淘宝客网站网上销售平台
  • 福州专业网站设计百度搜索风云榜小说总榜
  • 自学网站官网网址之家大全
  • 浙江省网站备案注销申请表优化大师哪个好
  • 建设用地办理信息网站软媒win7优化大师
  • wordpress相关文章调取企业seo推广的绝密诀窍曝光
  • 配置网站开发线上营销平台