2025/2/8第一次更新

This commit is contained in:
爱吃咸鱼小猫咪
2025-02-08 18:50:38 +08:00
commit d7af560866
26519 changed files with 5046029 additions and 0 deletions
+11
View File
@@ -0,0 +1,11 @@
## 1.0.0
初始版本发布
## 1.0.1
移除无用代码
## 1.0.2
修复confirm后数据不正确的问题
+13
View File
@@ -0,0 +1,13 @@
Copyright (c) 2023-present Tuniao Technology
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+93
View File
@@ -0,0 +1,93 @@
# 图鸟 UI vue3 uniapp Plugins - 更新用户信息弹框
![TuniaoUI vue3 uniapp](https://resource.tuniaokj.com/images/vue3/market/vue3-banner-min.jpg 'TuniaoUI vue3 uniapp')
[Tuniao UI vue3官方仓库](https://github.com/tuniaoTech/tuniaoui-rc-vue3-uniapp)
该组件一般用于更新用户的头像和昵称信息
## 组件安装
```bash
npm install tnuiv3p-tn-update-user-info-popup
```
## 组件位置
```typescript
import TnUpdateUserInfoPopup from 'tnuiv3p-tn-update-user-info-popup/index.vue'
```
## 平台差异说明
| App(vue) | H5 | 微信小程序 | 支付宝小程序 | ... |
| :------: | :-: | :--------: | :----------: | :----: |
| √ | √ | √ | √ | 适配中 |
## 基础使用
- 通过`v-model:show`来控制弹框的显示和隐藏
- 通过`v-model:avatar`绑定用户头像
- 通过`v-model:nickname`绑定用户昵称
通过`choose-avatar`事件将用户选择的头像上传到服务器,然后将返回的头像地址赋值给`v-model:avatar`
```vue
<script setup lang="ts">
import { ref } from 'vue'
const showPopup = ref<boolean>(false)
const nickname = ref<string>('')
const avatar = ref<string>('')
// 头像选择事件
const avatarChooseHandle = (url: string) => {
// 换成自己的上传接口
uni.uploadFile({
url: '服务器地址',
fileType: 'image',
filePath: url,
name: 'file',
success: (res) => {
const data = JSON.parse(res.data)
avatar.value = data.data.url
},
})
}
</script>
<template>
<TnButton @click="() => (showPopup = true)"> 修改用户信息 </TnButton>
<TnUpdateUserInfoPopup
v-model:show="showPopup"
v-model:nickname="nickname"
v-model:avatar="avatar"
@choose-avatar="avatarChooseHandle"
/>
</template>
```
## API
### Props
| 属性名 | 说明 | 类型 | 默认值 | 可选值 |
| ------------------ | ------------------------------------------------------------ | ------- | ------------------------------------------------------------ | ------- |
| show | 控制弹框显示、隐藏 | Boolean | `true` | `false` |
| avatar | 用户头像地址 | String | - | - |
| nickname | 用户昵称 | String | - | - |
| title | 弹框标题 | String | `获取您的昵称、头像` | - |
| tips | 弹框提示 | String | `获取用户头像、昵称,主要用于向用户提供具有辨识度的用户体验` | - |
| confirm-text | 弹框确认按钮文案 | String | `保 存` | - |
| confirm-bg-color | 弹框按钮背景颜色,可以使用图鸟内置的[背景色](https://vue3.tuniaokj.com/zh-CN/guide/style/background.html)、hex、rgb、rgba | String | `tn-type-primary` | - |
| confirm-text-color | 弹框按钮文字颜色,支持图鸟内置的[颜色值](https://vue3.tuniaokj.com/zh-CN/guide/style/text.html)、hex、rgb、rgba | String | `tn-white` | - |
### Events
| 事件名 | 说明 | 类型 |
| ------------- | ---------------- | -------------------------------------------- |
| choose-avatar | 头像选择事件 | `(url: string) => void` |
| confirm | 点击确认按钮事件 | `(avatar: string, nickname: string) => void` |
+2
View File
@@ -0,0 +1,2 @@
export * from './popup-custom'
export * from './use-popup'
@@ -0,0 +1,58 @@
import { computed, toRef } from 'vue'
import { useComponentColor, useNamespace } from '@tuniao/tnui-vue3-uniapp/hooks'
import type { CSSProperties } from 'vue'
import type { UpdateUserInfoPopupProps } from '../types'
export const useUpdateUserInfoPopupCustomStyle = (
props: UpdateUserInfoPopupProps
) => {
const ns = useNamespace('update-user-info-popup')
// 解析颜色
const [confirmBtnBgColorClass, confirmBtnBgColorStyle] = useComponentColor(
toRef(props, 'confirmBgColor'),
'bg'
)
const [confirmBtnTextColorClass, confirmBtnTextColorStyle] =
useComponentColor(toRef(props, 'confirmTextColor'), 'text')
// 提交按钮类和样式
const submitBtnClass = computed<string>(() => {
const cls: string[] = [ns.e('submit-btn')]
if (confirmBtnBgColorClass.value) {
cls.push(confirmBtnBgColorClass.value)
}
if (confirmBtnTextColorClass.value) {
cls.push(confirmBtnTextColorClass.value)
}
return cls.join(' ')
})
const submitBtnStyle = computed<CSSProperties>(() => {
const style: CSSProperties = {}
if (!confirmBtnBgColorClass.value) {
style.backgroundColor =
confirmBtnBgColorStyle.value || 'var(--tn-color-primary)'
}
if (confirmBtnTextColorStyle.value) {
style.color = confirmBtnTextColorStyle.value
} else if (!confirmBtnBgColorClass.value) {
style.color = 'var(--tn-color-white)'
}
if (!props.avatar || !props.nickname) {
style.backgroundColor = 'var(--tn-color-gray-disabled)'
style.color = 'var(--tn-color-gray-dark)'
}
return style
})
return {
ns,
submitBtnClass,
submitBtnStyle,
}
}
@@ -0,0 +1,78 @@
import { ref, watch } from 'vue'
import type { SetupContext } from 'vue'
import type {
UpdateUserInfoPopupEmits,
UpdateUserInfoPopupProps,
} from '../types'
export const useUpdateUserInfoPopup = (
props: UpdateUserInfoPopupProps,
emits: SetupContext<UpdateUserInfoPopupEmits>['emit']
) => {
// 显示更新用户信息弹框
const showUpdatePopup = ref<boolean>(false)
// 输入的用户名
const inputNickname = ref<string>(props.nickname)
watch(
() => props.show,
(val) => {
showUpdatePopup.value = val
},
{
immediate: true,
}
)
// 监听输入的用户名
const nickNameInputHandle = (e: any) => {
const value = e.detail.value
inputNickname.value = value
emits('update:nickname', value)
}
// 选择头像事件
// #ifdef MP-WEIXIN
const avatarChooseHandle = (e: any) => {
emits('choose-avatar', e.detail.avatarUrl)
}
// #endif
// #ifndef MP-WEIXIN
const avatarClickHandle = () => {
uni.chooseImage({
count: 1,
success: (res) => {
emits('choose-avatar', res.tempFilePaths[0])
},
})
}
// #endif
// 点击保存按钮
const submitBtnClickHandle = () => {
if (!inputNickname.value || !props.avatar) {
return
}
emits('confirm', props.avatar, inputNickname.value)
emits('update:show', false)
}
// 弹框关闭事件
const popupCloseHandle = () => {
emits('update:show', false)
}
return {
showUpdatePopup,
inputNickname,
nickNameInputHandle,
popupCloseHandle,
submitBtnClickHandle,
// #ifdef MP-WEIXIN
avatarChooseHandle,
// #endif
// #ifndef MP-WEIXIN
avatarClickHandle,
// #endif
}
}
+8
View File
@@ -0,0 +1,8 @@
import { withNoopInstall } from '@tuniao/tnui-vue3-uniapp/utils'
import UpdateUserInfoPopup from './index.vue'
export const TnUpdateUserInfoPopup = withNoopInstall(UpdateUserInfoPopup)
export default TnUpdateUserInfoPopup
export * from './types'
export type { TnUpdateUserInfoPopupInstance } from './instance'
+111
View File
@@ -0,0 +1,111 @@
<script lang="ts" setup>
import TnPopup from '@tuniao/tnui-vue3-uniapp/components/popup/src/popup.vue'
import TnIcon from '@tuniao/tnui-vue3-uniapp/components/icon/src/icon.vue'
import { updateUserInfoPopupEmits, updateUserInfoPopupProps } from './types'
import {
useUpdateUserInfoPopup,
useUpdateUserInfoPopupCustomStyle,
} from './composables'
const props = defineProps(updateUserInfoPopupProps)
const emit = defineEmits(updateUserInfoPopupEmits)
const {
showUpdatePopup,
inputNickname,
popupCloseHandle,
nickNameInputHandle,
submitBtnClickHandle,
// #ifdef MP-WEIXIN
avatarChooseHandle,
// #endif
// #ifndef MP-WEIXIN
avatarClickHandle,
// #endif
} = useUpdateUserInfoPopup(props, emit)
const { ns, submitBtnClass, submitBtnStyle } =
useUpdateUserInfoPopupCustomStyle(props)
</script>
<template>
<TnPopup
v-model="showUpdatePopup"
open-direction="bottom"
close-btn
:safe-area-inset-bottom="false"
@close="popupCloseHandle"
>
<view :class="[ns.b()]">
<view :class="[ns.e('title')]">
{{ title }}
</view>
<view :class="[ns.e('tips')]">
{{ tips }}
</view>
<view :class="[ns.e('avatar')]">
<!-- #ifdef MP-WEIXIN -->
<button
class="btn-reset"
open-type="chooseAvatar"
@chooseavatar="avatarChooseHandle"
>
<view :class="[ns.em('avatar', 'container')]">
<image
v-if="avatar"
:class="[ns.em('avatar', 'image')]"
:src="avatar"
mode="aspectFill"
/>
<view v-else :class="[ns.em('avatar', 'empty')]">
<TnIcon name="clover-fill" />
</view>
<view :class="[ns.em('avatar', 'assist')]">
<TnIcon name="camera-fill" />
</view>
</view>
</button>
<!-- #endif -->
<!-- #ifndef MP-WEIXIN -->
<view
:class="[ns.em('avatar', 'container')]"
@tap.stop="avatarClickHandle"
>
<image
v-if="avatar"
:class="[ns.em('avatar', 'image')]"
:src="avatar"
mode="aspectFill"
/>
<view v-else :class="[ns.em('avatar', 'empty')]">
<TnIcon name="clover-fill" />
</view>
<view :class="[ns.em('avatar', 'assist')]">
<TnIcon name="camera-fill" />
</view>
</view>
<!-- #endif -->
</view>
<input
:class="[ns.e('nickname-input')]"
:value="inputNickname"
type="nickname"
placeholder="请输入昵称"
placeholder-style="color:var(--tn-color-gray);"
@input="nickNameInputHandle"
/>
<view
:class="[submitBtnClass]"
:style="submitBtnStyle"
@tap.stop="submitBtnClickHandle"
>
{{ confirmText }}
</view>
</view>
</TnPopup>
</template>
<style lang="scss" scoped>
@import './theme-chalk/index.scss';
</style>
+5
View File
@@ -0,0 +1,5 @@
import type UpdateUserInfoPopup from './index.vue'
export type TnUpdateUserInfoPopupInstance = InstanceType<
typeof UpdateUserInfoPopup
>
+34
View File
@@ -0,0 +1,34 @@
{
"name": "tnuiv3p-tn-update-user-info-popup",
"version": "1.0.2",
"description": "TuniaoUI vue3 uniapp 插件",
"keywords": [
"tn",
"tuniao",
"tuniao-ui",
"图鸟",
"uniapp",
"uniapp插件"
],
"license": "Apache 2.0",
"author": "Tuniao Technology Co., Ltd.",
"publishConfig": {
"access": "public"
},
"repository": {
"type": "git",
"url": "https://github.com/tuniaoTech/tnuiv3p-update-user-info-popup"
},
"bugs": {
"url": "https://github.com/tuniaoTech/tnuiv3p-update-user-info-popup/issues"
},
"peerDependencies": {
"@tuniao/tn-icon": "^1.4.0",
"@tuniao/tn-style": "^1.0.17",
"@tuniao/tnui-vue3-uniapp": "^1.0.15",
"sass": "^1.61.0",
"typescript": "^5.1.6",
"vue": "^3.2.47"
},
"gitHead": ""
}
+110
View File
@@ -0,0 +1,110 @@
@use './mixins/mixins.scss' as *;
@include b(update-user-info-popup) {
padding: 50rpx 30rpx 60rpx 30rpx;
.btn-reset {
background-color: transparent;
border-radius: 0rpx;
color: unset;
font-size: unset;
line-height: unset;
overflow: visible;
padding: 0rpx;
&::after {
border: none;
border-radius: 0rpx;
}
}
@include e(title) {
font-size: 36rpx;
font-weight: bold;
}
@include e(tips) {
color: var(--tn-color-gray);
font-size: 26rpx;
margin-top: 12rpx;
}
@include e(avatar) {
position: relative;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
margin-top: 60rpx;
@include m(container) {
position: relative;
width: 180rpx;
height: 180rpx;
border-radius: 50%;
&::before {
content: '';
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
width: 110%;
height: 110%;
border-radius: 50%;
background-color: rgba(255, 255, 255, 0.4);
box-shadow: 0rpx 0rpx 80rpx 0rpx rgba(0, 0, 0, 0.15);
}
}
@include m(empty) {
position: relative;
width: 100%;
height: 100%;
border-radius: 50%;
background-color: var(--tn-color-gray-light);
color: var(--tn-color-grey-light);
display: flex;
align-items: center;
justify-content: center;
font-size: 100rpx;
}
@include m(assist) {
position: absolute;
right: -10rpx;
bottom: -12rpx;
width: 64rpx;
height: 64rpx;
border-radius: 50%;
background-color: #080808;
color: #fff;
display: flex;
align-items: center;
justify-content: center;
font-size: 40rpx;
border: 6rpx solid #fff;
}
}
@include e(nickname-input) {
position: relative;
width: 100%;
height: 80rpx;
background-color: var(--tn-color-gray-light);
margin-top: 50rpx;
padding: 0rpx 20rpx;
border-radius: 12rpx;
font-size: 28rpx;
}
@include e(submit-btn) {
position: relative;
width: 100%;
margin-top: 70rpx;
display: flex;
align-items: center;
justify-content: center;
padding: 26rpx 0rpx;
border-radius: 12rpx;
}
}
@@ -0,0 +1,5 @@
$namespace: 'tn' !default;
$common-separator: '-' !default;
$element-separator: '__' !default;
$modifier-separator: '--' !default;
$state-prefix: 'is-' !default;
@@ -0,0 +1,105 @@
@use 'config';
// BEM support Func
// 块(Block):代表一个独立的组件或页面中的一个大型部分。块可以看作是一个命名空间,用于包含相关元素和修饰符。例如,一个导航栏可以被视为一个块。
// 元素(Element):代表块的一部分,但不能独立存在。元素总是属于一个块,并且与该块紧密相关。元素由块名称和元素名称组成,中间由双下划线(__)连接。例如,一个导航栏可以包含多个链接,链接可以被视为导航栏块的元素。
// 修饰符(Modifier):代表块或元素的变体或状态。修饰符用于修改块或元素的外观或行为。修饰符由块名称或元素名称,连字符(-)和修饰符名称组成。例如,一个导航栏可以具有活动状态或浅色主题,这些状态可以通过添加修饰符类进行实现。
@function selectorToString($selector) {
$selector: inspect($selector);
$selector: str-slice($selector, 2, -2);
@return $selector;
}
@function containsModifier($selector) {
$selector: selectorToString($selector);
@if str_index($selector, config.$modifier-separator) {
@return true;
} @else {
@return false;
}
}
@function containWhenFlag($selector) {
$selector: selectorToString($selector);
@if str-index($selector, '.' + config.$state-prefix) {
@return true;
} @else {
@return false;
}
}
@function containPseudoClass($selector) {
$selector: selectorToString($selector);
@if str-index($selector, ':') {
@return true;
} @else {
@return false;
}
}
@function hitAllSpecialNestRule($selector) {
@return containsModifier($selector) or containWhenFlag($selector) or
containPseudoClass($selector);
}
// join var name
// joinVarName(('button', 'text-color')) => '--tn-button-text-color'
@function joinVarName($list) {
$name: '--' + config.$namespace;
@each $item in $list {
@if $item != '' {
$name: $name + '-' + $item;
}
}
@return $name;
}
// getCssVarName('button', 'text-color') => '--tn-button-text-color'
@function getCssVarName($args...) {
@return joinVarName($args);
}
// getCssVar('button', 'text-color') => var(--tn-button-text-color)
@function getCssVar($args...) {
@return var(#{joinVarName($args)});
}
// getCssVarWithDefault('button', 'text-color', 'red') => var(--tn-button-text-color, red)
@function getCssVarWithDefault($args, $default) {
@return var(#{joinVarName($args)}, #{$default});
}
// bem('block', 'element', ''modifier) => 'tn-block__element--modifier'
@function bem($block, $element: '', $modifier: '') {
$name: config.$namespace + config.$common-separator + $block;
@if $element != '' {
$name: $name + config.$element-separator + $element;
}
@if $modifier != '' {
$name: $name + config.$modifier-separator + $modifier;
}
@return $name;
}
// 字符串替换
@function str-replace($string, $search, $replace) {
$index: str-index($string, $search);
@if $index {
@return str-slice($string, 1, $index - 1) + $replace +
str-replace(
str-slice($string, $index + str-length($search)),
$search,
$replace
);
}
@return $string;
}
@@ -0,0 +1,134 @@
@use 'function' as *;
// forward mixins
@forward 'config';
@forward 'function';
@use 'config' as *;
// BEM
@mixin b($block) {
$B: $namespace + '-' + $block !global;
.#{$B} {
@content;
}
}
@mixin e($element) {
$E: $element !global;
$selector: &;
$currentSelector: '';
@each $unit in $element {
$currentSelector: #{$currentSelector +
'.' +
$B +
$element-separator +
$unit +
','};
}
@if hitAllSpecialNestRule($selector) {
@at-root {
#{$selector} {
#{$currentSelector} {
@content;
}
}
}
} @else {
@at-root {
#{$currentSelector} {
@content;
}
}
}
}
@mixin m($modifier) {
$selector: &;
$currentSelector: '';
@each $unit in $modifier {
$currentSelector: #{$currentSelector +
$selector +
$modifier-separator +
$unit +
','};
}
@at-root {
#{$currentSelector} {
@content;
}
}
}
@mixin configurable-m($modifier, $E-flag: false) {
$selector: &;
$interpolation: '';
@if $E-flag {
$interpolation: $element-separator + $E-flag;
}
@at-root {
#{$selector} {
.#{$B + $interpolation + $modifier-separator + $modifier} {
@content;
}
}
}
}
@mixin spec-selector(
$specSelector: '',
$element: $E,
$modifier: false,
$block: $B
) {
$modifierCombo: '';
$elementCombo: '';
@if $modifier {
$modifierCombo: $modifier-separator + $modifier;
}
@if $element {
$elementCombo: $element-separator + $element;
}
@at-root {
#{&}#{$specSelector}.#{$block + $elementCombo + $modifierCombo} {
@content;
}
}
}
@mixin meb($modifier: false, $element: $E, $block: $B) {
$selector: &;
$modifierCombo: '';
@if $modifier {
$modifierCombo: $modifier-separator + $modifier;
}
@at-root {
#{$selector} {
.#{$block + $element-separator + $element + $modifierCombo} {
@content;
}
}
}
}
@mixin when($state) {
@at-root {
&.#{$state-prefix + $state} {
@content;
}
}
}
@mixin pseudo($pseudo) {
@at-root #{&}#{':#{$pseudo}'} {
@content;
}
}
+82
View File
@@ -0,0 +1,82 @@
import { buildProps, isBoolean, isString } from '@tuniao/tnui-vue3-uniapp/utils'
import type { ExtractPropTypes } from 'vue'
export const updateUserInfoPopupProps = buildProps({
/**
* @description 控制弹框显示、隐藏
*/
show: {
type: Boolean,
default: false,
},
/**
* @description 用户头像地址
*/
avatar: {
type: String,
default: '',
},
/**
* @description 用户昵称
*/
nickname: {
type: String,
default: '',
},
/**
* @description 弹框标题
*/
title: {
type: String,
default: '获取您的昵称、头像',
},
/**
* @description 弹框提示
*/
tips: {
type: String,
default: '获取用户头像、昵称,主要用于向用户提供具有辨识度的用户体验',
},
/**
* @description 弹框确认按钮文案
*/
confirmText: {
type: String,
default: '保 存',
},
/**
* @description 弹框按钮背景颜色,以tn开头使用图鸟内置的颜色
*/
confirmBgColor: {
type: String,
default: 'tn-type-primary',
},
/**
* @description 弹框按钮文字颜色,以tn开头使用图鸟内置的颜色
*/
confirmTextColor: {
type: String,
default: 'tn-white',
},
})
export const updateUserInfoPopupEmits = {
'update:show': (val: boolean) => isBoolean(val),
'update:avatar': (val: string) => isString(val),
'update:nickname': (val: string) => isString(val),
/**
* @description 点击弹框确认按钮时触发
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
confirm: (avatar: string, nickname: string) => true,
/**
* @description 选择头像后触发
*/
'choose-avatar': (val: string) => isString(val),
}
export type UpdateUserInfoPopupProps = ExtractPropTypes<
typeof updateUserInfoPopupProps
>
export type UpdateUserInfoPopupEmits = typeof updateUserInfoPopupEmits