| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308 |
- <template>
- <view
- class="search-content ss-flex ss-col-center ss-row-between"
- @tap="click"
- :style="[
- {
- borderRadius: radius + 'px',
- background: elBackground,
- height: height + 'px',
- width: width,
- },
- ]"
- :class="[{ 'border-content': navbar }]"
- >
- <view
- class="ss-flex ss-col-center"
- :class="[placeholderPosition === 'center' ? 'ss-row-center' : 'ss-row-left']"
- v-if="navbar"
- style="width: 100%"
- >
- <view
- class="search-icon _icon-search"
- :style="{ color: fontColor, margin: '0 10rpx' }"
- ></view>
- <view class="search-input ss-line-1" :style="{ color: fontColor }">
- {{ placeholder }}
- </view>
- <!-- 右侧扫一扫图标 -->
- <view
- v-if="showScan"
- class="scan-icon _icon-scan"
- :style="{ color: fontColor }"
- @tap.stop="onScan"
- style="margin-left: auto"
- >
- </view>
- </view>
- <uni-search-bar
- v-if="!navbar"
- class="ss-flex-1"
- :radius="data.borderRadius"
- :placeholder="data.placeholder"
- cancelButton="none"
- clearButton="none"
- @confirm="onSearch"
- v-model="state.searchVal"
- />
- <view class="keyword-link ss-flex">
- <view v-for="(item, index) in data.hotKeywords" :key="index">
- <view
- class="ss-m-r-16"
- :style="[{ color: data.textColor }]"
- @tap.stop="sheep.$router.go('/pages/goods/list', { keyword: item })"
- >
- {{ item }}
- </view>
- </view>
- </view>
- <view v-if="data.hotKeywords && data.hotKeywords.length && navbar" class="ss-flex">
- <button
- class="ss-reset-button keyword-btn"
- v-for="(item, index) in data.hotKeywords"
- :key="index"
- :style="[{ color: data.textColor, marginRight: '10rpx' }]"
- >
- {{ item }}
- </button>
- </view>
- </view>
- </template>
- <script setup>
- /**
- * 基础组件 - 搜索栏
- *
- * @property {String} elBackground - 输入框背景色
- * @property {String} iconColor - 图标颜色
- * @property {String} fontColor - 字体颜色
- * @property {Number} placeholder - 默认placeholder
- * @property {Number} topRadius - 组件上圆角
- * @property {Number} bottomRadius - 组件下圆角
- *
- * @slot keywords - 关键字
- * @event {Function} click - 点击组件时触发
- */
- import { computed, reactive } from 'vue';
- import sheep from '@/sheep';
- // 组件数据
- const state = reactive({
- searchVal: '',
- });
- // 事件页面
- const emits = defineEmits(['click']);
- // 接收参数
- const props = defineProps({
- data: {
- type: Object,
- default: () => ({}),
- },
- // 输入框背景色
- elBackground: {
- type: String,
- default: '',
- },
- height: {
- type: Number,
- default: 36,
- },
- // 图标颜色
- iconColor: {
- type: String,
- default: '#b0b3bf',
- },
- // 字体颜色
- fontColor: {
- type: String,
- default: '#b0b3bf',
- },
- // placeholder
- placeholder: {
- type: String,
- default: '这是一个搜索框',
- },
- // placeholder位置(left | center)
- placeholderPosition: {
- type: String,
- default: 'left',
- },
- // 是否显示扫一扫图标
- showScan: {
- type: Boolean,
- default: false,
- },
- radius: {
- type: Number,
- default: 10,
- },
- width: {
- type: String,
- default: '100%',
- },
- navbar: {
- type: Boolean,
- default: true,
- },
- });
- // 点击
- const click = () => {
- emits('click');
- };
- function onSearch(e) {
- if (e.value) {
- sheep.$router.go('/pages/goods/list', { keyword: e.value });
- setTimeout(() => {
- state.searchVal = '';
- }, 100);
- }
- }
- /**
- * 扫一扫功能
- */
- function onScan() {
- uni.scanCode({
- onlyFromCamera: false,
- sound: 'default',
- scanType: ['qrCode', 'barCode'],
- success: (res) => {
- showScanResult(res.result);
- },
- fail: (err) => {
- console.error(err);
- uni.showToast({
- title: err.errMsg === 'scanCode:fail cancel' ? '操作已取消' : '扫码失败',
- icon: 'error',
- });
- },
- });
- }
- /**
- * 检测是否为有效URL
- * @param {string} str 待检测字符串
- * @returns {boolean} 是否为有效URL
- */
- function isValidUrl(str) {
- try {
- // 微信环境专用验证方式
- const url = str.trim();
- return (
- (url.startsWith('http://') || url.startsWith('https://') || url.startsWith('ftp://')) &&
- // 简单长度验证
- url.length >= 10
- );
- } catch {
- return false;
- }
- }
- /**
- * 展示扫码结果并处理用户操作
- * @param {string} text 扫码得到的内容
- */
- function showScanResult(text) {
- const isUrl = isValidUrl(text);
- // 显示结果弹窗
- uni.showModal({
- title: '扫描结果',
- content: text,
- confirmText: isUrl ? '访问' : '复制',
- cancelText: '取消',
- success: (res) => {
- if (res.confirm) {
- if (isUrl) {
- handleUrl(text);
- } else {
- handleCopy(text);
- }
- }
- },
- });
- }
- /**
- * 处理URL跳转
- * @param {string} url 要访问的URL
- */
- function handleUrl(url) {
- // 方案1:跳转到webview页面(推荐)
- uni.navigateTo({
- url: `/pages/public/webview?url=${encodeURIComponent(url)}`,
- });
- // 方案2:直接复制链接(备选方案)
- /*
- uni.setClipboardData({
- data: url,
- success: () => {
- uni.showToast({
- title: '链接已复制,请在浏览器打开',
- icon: 'success',
- });
- },
- });
- */
- }
- /**
- * 处理文本复制
- * @param {string} text 要复制的文本
- */
- function handleCopy(text) {
- uni.setClipboardData({
- data: text,
- success: () => {
- uni.showToast({
- title: '已复制到剪贴板',
- icon: 'success',
- });
- },
- fail: () => {
- uni.showToast({
- title: '复制失败,请重试',
- icon: 'error',
- });
- },
- });
- }
- </script>
- <style lang="scss" scoped>
- .border-content {
- border: 2rpx solid #eee;
- }
- .search-content {
- flex: 1;
- // height: 80rpx;
- position: relative;
- .search-icon {
- font-size: 38rpx;
- margin-right: 20rpx;
- }
- .scan-icon {
- font-size: 38rpx;
- margin-right: 20rpx;
- }
- .keyword-link {
- position: absolute;
- right: 16rpx;
- top: 18rpx;
- }
- .search-input {
- font-size: 28rpx;
- }
- }
- </style>
|