TDesign——Input指定光标插入内容

简介: TDesign——Input指定光标插入内容

前言

setRangeText: setRangeText

在线预览:wordPackage

内容

<template>
  <t-tabs :default-value="1">
    <t-tab-panel :value="1" label="广告创意">
      <template #panel>
        <t-form ref="form" :rules="FORM_RULES" :data="formData" :required-mark="false" @submit="onSubmit">
          <!-- 标题 -->
          <t-card
            :title="`标题(${formData.dynamic_creative_elements.title_options.length}/10)`"
            header-bordered
            :style="{ width: '800px', margin: '20px 0 0 20px' }"
          >
            <t-form-item
              v-for="(titles, index) in formData.titles"
              :key="index"
              label="标题(1-30字)"
              :name="`titles[${index}].title`"
            >
              <t-input
                :id="`title_${index}`"
                v-model="formData.titles[index].title"
                placeholder="请输入标题"
                :maxlength="30"
                show-limit-number
                allow-input-over-max
                @change="handleWordPackageDelete($event, 1, index)"
              />
              <t-dropdown
                :options="wordPackageOptions"
                :min-column-width="100"
                @click="handleWordsPackage($event, 1, index)"
              >
                <span>
                  <t-button variant="text">
                    <template #icon><wallet-icon /></template>
                    插入词包
                  </t-button>
                </span>
              </t-dropdown>
              <span v-if="formData.titles.length > 1" @click="handleRemoveElement(1, index)">
                <t-button shape="square">
                  <template #icon><remove-icon size="2em" /></template>
                </t-button>
              </span>
            </t-form-item>
            <div>
              <t-button block :disabled="formData.titles.length === 10" @click="handleAddElement(1)">
                还可以添加{{ 10 - formData.titles.length }}个
              </t-button>
            </div>
          </t-card>
          <!-- 描述 -->
          <t-card
            :title="`描述(${formData.descriptions.length}/10)`"
            header-bordered
            :style="{ width: '800px', margin: '20px 0 0 20px' }"
          >
            <template #subtitle>
              <t-tooltip theme="light">
                <template #content>
                  <div style="width: 280px">
                    为使展示效果最佳,建议搜一搜描述文案字数 30 字以内
                    <t-image
                      src="https://qzonestyle.gdtimg.com/gdt_ui_proj/imghub/dist/search-desc-tip.png?max_age=31536000"
                    />
                  </div>
                </template>
                <help-circle-icon size="18px" />
              </t-tooltip>
            </template>
            <t-form-item
              v-for="(descriptions, index) in formData.descriptions"
              :key="index"
              label="描述(4-80字)"
              :name="`descriptions[${index}].description`"
            >
              <t-input
                :id="`desc_${index}`"
                v-model="formData.descriptions[index].description"
                placeholder="请输入描述"
                :maxlength="80"
                show-limit-number
                allow-input-over-max
                @change="handleWordPackageDelete($event, 2, index)"
              />
              <t-dropdown
                :options="wordPackageOptions"
                :min-column-width="100"
                @click="handleWordsPackage($event, 2, index)"
              >
                <span>
                  <t-button variant="text">
                    <template #icon><wallet-icon /></template>
                    插入词包
                  </t-button>
                </span>
              </t-dropdown>
              <span v-if="formData.descriptions.length > 1" @click="handleRemoveElement(2, index)">
                <t-button shape="square">
                  <template #icon><remove-icon size="2em" /></template>
                </t-button>
              </span>
            </t-form-item>
            <div>
              <t-button block :disabled="formData.descriptions.length === 10" @click="handleAddElement(2)">
                还可以添加{{ 10 - formData.descriptions.length }}个
              </t-button>
            </div>
          </t-card>
        </t-form>
      </template>
    </t-tab-panel>
  </t-tabs>
  <!-- 插入词包 | 添加关键词弹窗 -->
  <t-dialog
    v-model:visible="addDefaultKeywordVisible"
    @close="handleAddDefaultKeywordCancel"
    @confirm="handleAddDefaultKeywordConfirm"
  >
    <template #header>
      <div>
        <span>添加默认关键词</span>
        <t-tooltip theme="light">
          <template #content>
            <div style="width: 280px">
              插入关键词通配符的创意得到展现时,系统会根据匹配策略,将默认关键词替换为触发的关键词,
              提高创意与用户搜索词之间的相关性,同时创意中和用户搜索词一致的词汇,可能得到飘红展示。
              例如:如果广告主填写了“北京同城{鲜花}配送”,且购买了“玫瑰花”这个关键词,当用户搜索“北京哪里可以送玫瑰花”时,
              广告创意可能以“北京同城玫瑰花配送”展示给用户。即默认关键词“鲜花”被替换成用户搜索词中包含的关键词“玫瑰花”。
              <br />
              请为关键词词包设置默认关键词。触发的关键词替换时,如果用户搜索词过长,
              直接替换可能超过字数限制,或替换后命中审核黑词,此时系统将以广告主设置的默认关键词来替换并展现。
            </div>
          </template>
          <help-circle-icon size="18px" />
        </t-tooltip>
      </div>
    </template>
    <t-input v-model="defaultKeyword" placeholder="请输入关键词" :maxlength="30" show-limit-number />
  </t-dialog>
</template>
<script setup lang="ts">
import {ref} from 'vue'
import { HelpCircleIcon, InfoCircleFilledIcon, RemoveIcon, WalletIcon } from 'tdesign-icons-vue-next';
import { MessagePlugin } from 'tdesign-vue-next';
const props = defineProps({
  promote: Object,
  adcreativeId: String,
});
const formData: any = ref({
  dynamic_creative_elements: {
    title_options: [],
    description_options: [],
    image_options: [],
    video_options: [],
    brand: {
      brand_img: '',
      brand_name: '',
    },
  },
  page_type: 'PAGE_TYPE_CANVAS_WECHAT',
  page_spec: {},
  dynamic_creative_name: '',
  // 数据中转
  titles: [{ title: '' }],
  descriptions: [{ description: '' }],
  images: [],
  videos: [],
  brand: {},
});
const wordPackageOptions = [
  { content: '城市', value: 1, extends: '{{city}}' },
  { content: '日期', value: 2, extends: '{{day}}' },
  { content: '星期', value: 3, extends: '{{week}}' },
  { content: '关键词', value: 4 },
];
const wordPackagesArray = ref(['{{city}}', '{{day}}', '{{week}}']);
const addDefaultKeywordVisible = ref(false);
const defaultKeyword = ref('');
// 处理词包
const handleWordPackageDelete = (val: string, type: number, index: number) => {
  let inputValue = val;
  inputValue = (inputValue += '')
    .replace(/([^{]+|^)({[^{}]*?}+)/g, (e, t) => t)
    .replace(/{{[^{}]*?}([^}]|$)/g, (e, t) => t)
    .replace(/{{([^{}]*?)}}/g, (e) => (wordPackagesArray.value.includes(e) ? e : ''));
  if (type === 1) formData.value.titles[index].title = inputValue;
  if (type === 2) formData.value.descriptions[index].description = inputValue;
};
// 处理取消添加默认关键词
const handleAddDefaultKeywordCancel = () => {
  addDefaultKeywordVisible.value = false;
  defaultKeyword.value = '';
};
// 处理确定添加默认关键词
const handleAddDefaultKeywordConfirm = () => {
  const wordPackages = JSON.parse(localStorage.getItem('wordPackages'));
  const keyword = `{${defaultKeyword.value}}`;
  if (!wordPackagesArray.value.includes(keyword)) wordPackagesArray.value.push(keyword);
  handleWordsPackage({ extends: keyword }, wordPackages.type, wordPackages.index);
  addDefaultKeywordVisible.value = false;
  defaultKeyword.value = '';
};
// 处理词包插入 | NOTE 这里不考虑IE浏览器
const handleWordsPackage = (val: any, type: number, index: number) => {
  localStorage.setItem('wordPackages', JSON.stringify({ type, index }));
  const id = `#${['title_', 'desc_'][type - 1]}${index} input`;
  const input = document.querySelector(id) as HTMLInputElement;
  if (val.value === 4) addDefaultKeywordVisible.value = true;
  else input.setRangeText(val.extends);
  if (type === 1) formData.value.titles[index].title = input.value;
  if (type === 2) formData.value.descriptions[index].description = input.value;
};
// TODO 标题和描述元素抽离成组件
// 处理标题和描述的元素的移除
const handleRemoveElement = (option: number, index: number) => {
  const { titles, descriptions } = formData.value;
  const options = {
    1: () => {
      titles.splice(index, 1);
    },
    2: () => {
      descriptions.splice(index, 1);
    },
  };
  options[option]();
};
// 处理标题和描述的元素添加
const handleAddElement = (option: number) => {
  const { titles, descriptions } = formData.value;
  const options = {
    1: () => {
      titles.push({
        title: '',
      });
    },
    2: () => {
      descriptions.push({
        description: '',
      });
    },
  };
  options[option]();
};
// 表单校验规则
const FORM_RULES = {
  title: [
    { required: true, message: '请输入标题', type: 'error', trigger: 'blur' },
    {
      validator: (val: any) => val.length <= 30,
      message: '标题字数不能超过30字',
      type: 'error',
      trigger: 'change',
    },
  ],
  description: [
    { required: true, message: '请输入描述', type: 'error', trigger: 'blur' },
    {
      validator: (val: any) => val.length <= 80,
      message: '描述字数不能超过80字',
      type: 'error',
      trigger: 'change',
    },
  ],
};
</script>
<style lang="less" scoped>
:deep(.t-tab-panel) {
  margin-left: var(--td-comp-margin-xxxl);
}
.promote-label {
  margin-right: var(--td-comp-margin-xxl);
}
:deep(.t-input) {
  border: transparent;
  border-bottom: 1px solid var(--td-border-level-2-color) !important;
}
:deep(.t-card__title) {
  margin-right: var(--td-comp-margin-xs);
}
:deep(.t-icon) {
  vertical-align: sub;
}
:deep(.t-button--variant-text) {
  border-bottom: 1px solid var(--td-border-level-2-color);
  color: var(--td-text-color-secondary);
}
.sub-text {
  margin-top: 5px;
  font: var(--td-font-body-small);
  color: var(--td-text-color-placeholder);
}
:deep(.t-collapse-panel__icon--left) {
  position: relative;
  top: -26px;
}
.select__overlay-option .t-select-option {
  height: 100%;
  padding: 8px;
}
.user-option-info {
  margin-left: 16px;
}
.user-option {
  display: flex;
}
.select-panel-footer {
  border-top: 1px solid var(--td-component-stroke);
  padding: 6px;
}
</style>

学无止境,谦卑而行.

目录
相关文章
|
JavaScript Java 微服务
spring-cloud-alibaba 组件版本关系
spring-cloud-alibaba 组件版本关系
2954 0
|
前端开发 JavaScript API
TS 中的类型验算,高级通用 API 实现
这篇文章介绍了一些常用的类型通用API封装,包括TS内置类型和关键字的使用,以及TS compiler内部实现的类型。文章截取了一些常用的类型定义和API示例,如Partial、Required、Readonly、NonNullable、Parameters等。还介绍了一些常用的TS关键字,如extends、infer、keyof、typeof、in等。此外,文章还提供了一些实现示例,如Optional API、GetOptional API和UnionToIntersection API。该文章会不断更新。
310 0
TS 中的类型验算,高级通用 API 实现
|
JavaScript
vite的快的原因居然如此简单!探秘其依赖预加载机制
【8月更文挑战第1天】探秘vite预加载机制
378 4
vite的快的原因居然如此简单!探秘其依赖预加载机制
|
JavaScript 小程序
微信小程序 - 调用自定义组件内部方法
微信小程序 - 调用自定义组件内部方法
911 0
|
Web App开发 JSON 数据格式
【Azure Developer】浏览器查看本地数据文件时遇见跨域问题(CORS)
Access to XMLHttpRequest at 'file:///C:/Users/.../failedrequests.json' from origin 'null' has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: http, data, isolated-app, chrome-extension, chrome-untrusted, https, edge. reportdata/failedrequests.json:1 Fail
382 1
|
Ubuntu 数据安全/隐私保护
安装Ubuntu16.04卡在Ubuntu的logo界面解决方法
安装Ubuntu16.04卡在Ubuntu的logo界面解决方法
3498 0
安装Ubuntu16.04卡在Ubuntu的logo界面解决方法
|
JSON JavaScript 小程序
组件的插槽以及组件通信
这篇文章介绍了微信小程序中组件的插槽使用和组件间通信的方法,包括单个插槽、多个插槽的运用,以及属性绑定、事件绑定和获取组件实例的通信方式。
组件的插槽以及组件通信
|
JSON 小程序 JavaScript
面试官说,布局小程序页面记得用TDesign组件库
面试官说,布局小程序页面记得用TDesign组件库
|
SQL 关系型数据库 MySQL
关系型数据库的并发处理能力限制
【5月更文挑战第3天】关系型数据库的并发处理能力限制
495 8
关系型数据库的并发处理能力限制
|
小程序
【微信小程序-原生开发】列表 - 拖拽排序(官方组件 movable-area 和 movable-view 的用法)
【微信小程序-原生开发】列表 - 拖拽排序(官方组件 movable-area 和 movable-view 的用法)
1905 0