Commit ee9ea4cc authored by tyyin lan's avatar tyyin lan

build: project initialization

parents
root = true
[*]
indent_style = space
charset = utf-8
indent_size = 2
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false
insert_final_newline = false
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
.DS_Store
dist
*.local
# Editor directories and files
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
pnpm dlx commitlint --edit $1
npm run lint:lint-staged
src/wxcomponents/
\ No newline at end of file
node_modules
build
dist
src/wxcomponents
\ No newline at end of file
{
"recommendations": [
"Vue.volar",
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"EditorConfig.EditorConfig",
"bradlc.vscode-tailwindcss",
"stylelint.vscode-stylelint",
"redhat.vscode-yaml",
],
"unwantedRecommendations": [
"bradlc.vscode-tailwindcss"
]
}
\ No newline at end of file
{
"editor.formatOnSave": true,
"editor.formatOnPaste": true,
"editor.tabSize": 2,
"[vue]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[css]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"extensions.disabledRecommendations": [
"antfu.unocss"
],
"files.associations": {
"*.css": "tailwindcss"
},
"editor.quickSuggestions": {
"strings": "on"
},
"tailwindCSS.includeLanguages": {
"wxml": "html"
}
}
\ No newline at end of file
import type { UserConfig } from '@commitlint/types'
const configuration: UserConfig = {
extends: ['@commitlint/config-conventional'],
rules: {
'body-leading-blank': [2, 'always'],
'footer-leading-blank': [1, 'always'],
'subject-empty': [2, 'never'],
'type-empty': [2, 'never'],
'type-enum': [
2,
'always',
[
'feat',
'fix',
'perf',
'style',
'docs',
'test',
'refactor',
'build',
'ci',
'chore',
'revert',
'types',
'release',
],
],
},
}
export default configuration
import js from '@eslint/js'
import globals from 'globals'
import tseslint from 'typescript-eslint'
import pluginVue from 'eslint-plugin-vue'
import { globalIgnores } from 'eslint/config'
import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'
import vueParser from 'vue-eslint-parser'
export default tseslint.config(
globalIgnores(['**/node_modules/', '.git/', 'dist/', 'public/', 'src/wxcomponents/']),
js.configs.recommended,
tseslint.configs.recommended,
pluginVue.configs['flat/essential'],
{
files: ['**/*.{js,mjs,ts,mts,vue,jsx,tsx}'],
languageOptions: {
globals: {
...globals.browser,
ViteEnv: 'readonly',
AnyObject: 'readonly',
uni: 'readonly',
/* 自定义 ts 全局类型 */
UploadFilePayload: 'readonly',
/* 内置API */
UniApp: 'readonly',
getCurrentPages: 'readonly',
},
},
},
{
files: ['**/*.{ts,vue}'],
languageOptions: {
parser: vueParser,
parserOptions: {
parser: tseslint.parser,
ecmaVersion: 'latest',
ecmaFeatures: {
jsx: true,
},
},
},
rules: {
'vue/multi-word-component-names': 'off',
'@typescript-eslint/no-unused-vars': [
'error',
{
args: 'all',
argsIgnorePattern: '^_',
caughtErrors: 'all',
caughtErrorsIgnorePattern: '^_',
destructuredArrayIgnorePattern: '^_',
varsIgnorePattern: '^_',
ignoreRestSiblings: true,
},
],
'@typescript-eslint/no-empty-object-type': 'warn',
},
},
eslintPluginPrettierRecommended,
)
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<script>
var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') ||
CSS.supports('top: constant(a)'))
document.write(
'<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' +
(coverSupport ? ', viewport-fit=cover' : '') + '" />')
</script>
<title></title>
<!--preload-links-->
<!--app-context-->
</head>
<body>
<div id="app"><!--app-html--></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
export default {
'*.md': ['prettier --write'],
'package.json': ['prettier --write'],
'!(package).json': ['prettier --write--parser json'],
'*.{css,scss,postcss,less}': ['stylelint --fix', 'prettier --write'],
'*.{js,jsx,ts,tsx}': ['eslint --fix --no-warn-ignored', 'prettier --write'],
'*.vue': ['eslint --fix --no-warn-ignored', 'stylelint --fix', 'prettier --write', 'echo "统一格式化完成🌸"'],
}
{
"name": "hxyj-mini",
"version": "0.0.0",
"scripts": {
"dev": "uni -p mp-weixin",
"build": "uni build -p mp-weixin",
"type-check": "vue-tsc --noEmit",
"lint:lint-staged": "lint-staged -c ./lint-staged.config.mjs",
"lint:prettier": "prettier --write \"src/**/*.{js,ts,json,tsx,css,scss,vue,html,md}\"",
"lint:stylelint": "stylelint \"./src/**/*.{html,vue,css,scss}\" --fix",
"postinstall": "weapp-tw patch",
"prepare": "husky"
},
"dependencies": {
"@dcloudio/uni-app": "3.0.0-4030620241128001",
"@dcloudio/uni-app-harmony": "3.0.0-4030620241128001",
"@dcloudio/uni-app-plus": "3.0.0-4030620241128001",
"@dcloudio/uni-components": "3.0.0-4030620241128001",
"@dcloudio/uni-h5": "3.0.0-4030620241128001",
"@dcloudio/uni-mp-weixin": "3.0.0-4030620241128001",
"pinia": "^3.0.3",
"vue": "^3.4.21",
"vue-i18n": "^9.1.9"
},
"devDependencies": {
"@commitlint/cli": "^19.8.1",
"@commitlint/config-conventional": "^19.8.1",
"@commitlint/types": "^19.8.1",
"@dcloudio/types": "^3.4.8",
"@dcloudio/uni-automator": "3.0.0-4030620241128001",
"@dcloudio/uni-cli-shared": "3.0.0-4030620241128001",
"@dcloudio/uni-stacktracey": "3.0.0-4030620241128001",
"@dcloudio/vite-plugin-uni": "3.0.0-4030620241128001",
"@eslint/js": "^9.28.0",
"@tailwindcss/postcss": "^4.1.8",
"@vue/runtime-core": "^3.4.21",
"@vue/tsconfig": "^0.1.3",
"eslint": "^9.28.0",
"eslint-config-prettier": "^10.1.5",
"eslint-plugin-prettier": "^5.4.1",
"eslint-plugin-vue": "^10.1.0",
"globals": "^16.2.0",
"husky": "^9.1.7",
"lint-staged": "^16.1.0",
"prettier": "^3.5.3",
"stylelint": "^16.20.0",
"stylelint-config-recess-order": "^6.0.0",
"stylelint-config-recommended-vue": "^1.6.0",
"stylelint-config-standard": "^38.0.0",
"stylelint-order": "^7.0.0",
"tailwindcss": "^4.1.8",
"typescript": "^4.9.4",
"typescript-eslint": "^8.33.1",
"vite": "5.2.8",
"vue-eslint-parser": "^10.1.3",
"vue-tsc": "^1.0.24",
"weapp-tailwindcss": "^4.1.8"
},
"volta": {
"node": "20.19.0"
}
}
This diff is collapsed.
export default {
// plugins: ['prettier-plugin-tailwindcss'],
experimentalTernaries: false,
printWidth: 120,
tabWidth: 2,
useTabs: false,
semi: false,
singleQuote: true,
quoteProps: 'as-needed', // 对象属性引号问题
jsxSingleQuote: true,
trailingComma: 'all', // 尾随逗号
bracketSpacing: true,
bracketSameLine: false,
arrowParens: 'always', // 箭头函数单个参数 提供括号支持
requirePragma: false,
insertPragma: false,
proseWrap: 'preserve',
htmlWhitespaceSensitivity: 'css',
vueIndentScriptAndStyle: false,
endOfLine: 'auto',
singleAttributePerLine: false, // 在 HTML、Vue和JSX中强制每行使用单一属性
}
<script setup lang="ts">
import { onLaunch, onShow, onHide } from '@dcloudio/uni-app'
onLaunch(() => {
console.log('App Launch')
})
onShow(() => {
console.log('App Show')
})
onHide(() => {
console.log('App Hide')
})
</script>
<style src="./styles/app.css"></style>
interface ConfigInterface {
HOME_PATH: string
USER_PAGE: string
}
const PAGE_CONFIG: Readonly<ConfigInterface> = {
HOME_PATH: '/pages/index/index',
USER_PAGE: '/pages/user/user',
}
export default PAGE_CONFIG
type Env = 'dev' | 'prod'
/* 此处修改环境 */
const currentEnv: Env = 'dev' // 测试环境
// const currentEnv: Env = 'prod' // 生产环境
const DOMAIN_NAME: Readonly<Record<Env, string>> = {
dev: '',
prod: '',
} as const
const CONFIG: Readonly<{ API_BASE_URL: string; DOMAIN_NAME: string }> = {
API_BASE_URL: `${DOMAIN_NAME[currentEnv]}/api/rest`,
DOMAIN_NAME: DOMAIN_NAME[currentEnv],
}
export default CONFIG
import { createSSRApp } from 'vue'
import App from './App.vue'
import * as Pinia from 'pinia'
export function createApp() {
const app = createSSRApp(App)
app.use(Pinia.createPinia())
return {
app,
Pinia,
}
}
{
"name": "",
"appid": "",
"description": "",
"versionName": "1.0.0",
"versionCode": "100",
"transformPx": false,
/* 5+App特有相关 */
"app-plus": {
"usingComponents": true,
"nvueStyleCompiler": "uni-app",
"compilerVersion": 3,
"splashscreen": {
"alwaysShowBeforeRender": true,
"waiting": true,
"autoclose": true,
"delay": 0
},
/* 模块配置 */
"modules": {},
/* 应用发布信息 */
"distribute": {
/* android打包配置 */
"android": {
"permissions": [
"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
"<uses-permission android:name=\"android.permission.VIBRATE\"/>",
"<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
"<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.CAMERA\"/>",
"<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
"<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
"<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
"<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
"<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
"<uses-feature android:name=\"android.hardware.camera\"/>",
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
]
},
/* ios打包配置 */
"ios": {},
/* SDK配置 */
"sdkConfigs": {}
}
},
/* 快应用特有相关 */
"quickapp": {},
/* 小程序特有相关 */
"mp-weixin": {
"appid": "wx928ea2e7e8ec0231",
"setting": {
"urlCheck": false
},
"usingComponents": true
},
"mp-alipay": {
"usingComponents": true
},
"mp-baidu": {
"usingComponents": true
},
"mp-toutiao": {
"usingComponents": true
},
"uniStatistics": {
"enable": false
},
"vueVersion": "3"
}
\ No newline at end of file
{
"pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
{
"path": "pages/index/index",
"style": {
"navigationBarTitleText": "uni-app"
}
}
],
"globalStyle": {
"navigationBarTextStyle": "black",
"navigationBarTitleText": "uni-app",
"navigationBarBackgroundColor": "#F8F8F8",
"backgroundColor": "#F8F8F8",
"usingComponents": {
"van-button": "/wxcomponents/vant/button/index"
}
}
}
\ No newline at end of file
<script setup lang="ts"></script>
<template>
<view>
<view class="text-center mb-[30rpx]">
<text class="text-[#ff6700] text-[40rpx]">Home Page</text>
</view>
<van-button type="primary">按钮测试</van-button>
</view>
</template>
import type { App } from 'vue'
import { createPinia } from 'pinia'
export const store = createPinia()
export function setupStore(app: App) {
app.use(store)
}
import { defineStore } from 'pinia'
export interface UserInfo {
nickName: string
avatar: string
intro: string
id: number | null
telephone: number | string
}
export interface CreationCountInfo {
expireDate: string
totalCount: number | null
willExpireCount: number | null
}
interface UserState {
isLogin: boolean
token: string
userInfo: UserInfo
creationCountInfo: CreationCountInfo
}
const defaultUserInfo = {
nickName: '',
avatar: '',
intro: '',
id: null,
telephone: '',
}
const defaultCreationCountInfo = {
expireDate: '',
totalCount: null,
willExpireCount: null,
}
export const useUserStore = defineStore('user-store', {
state: (): UserState => ({
isLogin: uni.getStorageSync('isLogin') || false,
token: uni.getStorageSync('token') || '',
userInfo: uni.getStorageSync('userInfo') || defaultUserInfo,
creationCountInfo: uni.getStorageSync('creationCountInfo') || defaultCreationCountInfo,
}),
actions: {
logout() {
this.isLogin = false
this.token = ''
this.userInfo = defaultUserInfo
uni.removeStorageSync('isLogin')
uni.removeStorageSync('token')
uni.removeStorageSync('userInfo')
uni.removeStorageSync('creationCountInfo')
},
setToken(token: string) {
if (token) {
this.isLogin = true
} else {
this.isLogin = false
}
uni.setStorageSync('isLogin', this.isLogin)
this.token = token
uni.setStorageSync('token', token)
},
setUserInfo(userInfo: UserInfo) {
this.userInfo = userInfo
uni.setStorageSync('userInfo', userInfo)
},
recordState() {
// ss.set(LOCAL_NAME, this.$state)
},
setCreationCountInfo(info: CreationCountInfo) {
this.creationCountInfo = info
uni.setStorageSync('creationCountInfo', info)
},
updateCreationCountInfo() {},
},
})
@import 'weapp-tailwindcss/css';
@import './theme.css';
@import './utilities.css';
@source not "dist";
@theme {
--color-theme-color: #00a2ea;
}
@utility flex-center {
@apply flex items-center justify-center;
}
type Primitive = string | number | boolean | null | undefined
type SerializableValue = Primitive | SerializableObject | SerializableArray
export interface SerializableObject {
[key: string]: SerializableValue
}
// interface SerializableArray extends Array<SerializableValue> {}
type SerializableArray = Array<SerializableValue>
/**
* 递归地将对象或数组构建成 query string 的键值对部分。
* @param obj 当前要处理的对象或值。
* @param prefix 当前键的前缀 (例如 "user" 或 "user[address]")。
* @param add 回调函数,用于将最终的 key-value 对添加到结果中。
*/
function buildQueryParts(
obj: SerializableValue,
prefix: string | undefined,
add: (key: string, value: Primitive) => void,
): void {
if (obj === null || obj === undefined) {
// 如果需要,可以处理将 'key=' 这种形式加入 query string
// if (prefix) {
// add(prefix, null); // or '', depends on how backend handles it
// }
return
}
if (Array.isArray(obj)) {
if (obj.length === 0 && prefix) {
// 可选:如果数组为空,有些后端期望 key[]='' 或类似的来表示空数组
// add(`${prefix}[]`, '');
} else {
obj.forEach((value, _index) => {
// 使用 prefix[] 来表示数组元素,这是最常见的做法
// 或者使用 prefix[index] 如果后端需要特定索引
const arrayKey = `${prefix}[]` // e.g., colors[]
// const arrayKey = `${prefix}[${index}]`; // e.g., colors[0]
buildQueryParts(value, arrayKey, add)
})
}
} else if (typeof obj === 'object' && !(obj instanceof Date)) {
// Date 对象应该被视为 primitive
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
const newPrefix = prefix ? `${prefix}[${key}]` : key
buildQueryParts(obj[key], newPrefix, add)
}
}
} else {
// Primitive value (string, number, boolean, Date)
if (!prefix) {
// This case should ideally not be hit if the initial call is with an object/array
// Or if you want to allow top-level primitive (e.g. ?myValue=10 - less common for structured data)
throw new Error('Cannot serialize a primitive value without a key prefix at top level of structured data.')
}
add(prefix, obj as Primitive)
}
}
/**
* 将深层嵌套的对象转换为 URL Query 字符串 (使用方括号表示法)。
* 例如:{ user: { name: 'lisa' }, tags: ['js', 'ts'] }
* 转换为:user[name]=lisa&tags[]=js&tags[]=ts
*
* @param data 要转换的对象。
* @returns 生成的 URL Query 字符串。
*/
export function objectToDeepQueryString(data: SerializableObject | SerializableArray): string {
if (!data || typeof data !== 'object') {
return ''
}
const params: string[] = []
const add = (key: string, value: Primitive) => {
let processedValue: string
if (value === null || value === undefined) {
// 决定如何处理 null/undefined 值
// 选项1: 忽略 (common) - 已在 buildQueryParts 顶部处理
// 选项2: key= (空字符串)
// processedValue = '';
// 选项3: 包含 null/undefined 字符串 (less common)
// processedValue = String(value);
// 当前 buildQueryParts 会直接 return,所以这里不会接收到 null/undefined
// 如果修改 buildQueryParts 以传递 null/undefined,则在这里处理
processedValue = '' // Defaulting to empty string if add is called with null/undefined
} else if ((value as unknown as object) instanceof Date) {
processedValue = (value as unknown as Date).toISOString() // ISO 8601 格式是常见的日期序列化方式
} else {
processedValue = String(value)
}
// 重要: key 中的方括号通常不进行 URL 编码,但值需要编码。
// encodeURIComponent 会编码 '[', ']' 等。
// 如果后端期望字面上的 '[]',则不能对整个 key 编码。
// 这里我们假设 key 构造正确,只编码 value。
params.push(`${key}=${encodeURIComponent(processedValue)}`)
}
if (Array.isArray(data)) {
// 如果顶层是一个数组,可能需要一个默认的根键名
buildQueryParts(data, 'data', add) // 例如: data[]=value1&data[]=value2
} else {
buildQueryParts(data, undefined, add)
}
return params.join('&')
}
import URL_CONFIG from '@/config/url'
import { objectToDeepQueryString, type SerializableObject } from '@/utils/query-string'
import { useUserStore } from '@/store/modules/user'
import PAGE_CONFIG from '@/config/page-config'
interface PagingInfoParams {
pageNo: number
pageSize: number
}
type RequestOptions = Omit<UniApp.RequestOptions, 'url' | 'data' | 'method'> & {
ignoreErrorCheck?: boolean
ignoreToken?: boolean
ignoreLoginHandle?: boolean
baseUrl?: string
}
type Payload = { pagingInfo?: PagingInfoParams; params?: AnyObject; [key: string]: unknown } | null
export interface Response<T> {
data: T
message: string | null
code: number
pagingInfo?: {
pageNo: number
pageSize: number
totalPages: number
}
}
const service = {
get<T = unknown>(url: string, options: RequestOptions = {}): Promise<Response<T>> {
return new Promise((resolve, reject) => {
uni.request({
...options,
method: 'GET',
url: (options.baseUrl ? options.baseUrl : URL_CONFIG.API_BASE_URL) + url,
success: (res) => {
const data = res.data as unknown as Response<T>
if (res.statusCode === 200) {
resolve(data)
return
}
reject(res.errMsg)
},
fail: (res) => reject(res),
})
})
},
post<T = unknown>(
url: string,
payload: string | AnyObject | ArrayBuffer | null,
options: RequestOptions = {},
): Promise<Response<T>> {
return new Promise((resolve, reject) => {
uni.request({
...options,
url: (options.baseUrl ? options.baseUrl : URL_CONFIG.API_BASE_URL) + url,
method: 'POST',
data: payload || '',
success: (res) => {
const data = res.data as unknown as Response<T>
if (res.statusCode === 200) {
resolve(data)
return
}
reject(res.errMsg)
},
fail: (res) => reject(res.errMsg),
})
})
},
uploadFile<T = unknown>(url: string, payload: UploadFilePayload, options: RequestOptions = {}): Promise<Response<T>> {
return new Promise((resolve, reject) => {
const uploadTask = uni.uploadFile({
url: (options.baseUrl ? options.baseUrl : URL_CONFIG.API_BASE_URL) + url,
filePath: payload.filePath,
name: payload.name || 'file',
formData: payload.formData || {},
header: options.header || {},
success: (res) => {
const data = res.data as unknown as Response<T>
if (res.statusCode === 200) {
resolve(data)
return
}
reject(res.errMsg)
},
fail: (res) => reject(res.errMsg),
})
if (payload.uploadTaskFun) {
payload.uploadTaskFun(uploadTask)
}
})
},
}
let logoutModalFlag = 0
/* 身份过期处理 */
function handleLogout() {
if (logoutModalFlag === 0) {
const userStore = useUserStore()
logoutModalFlag = 1
const content = userStore.token ? '当前身份已过期,是否重新登录?' : '当前未登录,是否前往登录?'
userStore.logout()
userStore.setCreationCountInfo({ expireDate: '', totalCount: null, willExpireCount: null })
uni.showModal({
title: '提示',
content,
confirmText: '去登录',
success: ({ confirm }) => {
if (confirm) {
logoutModalFlag = 0
const pages = getCurrentPages()
const path = pages[pages.length - 1].route || ''
// uni.navigateTo({ url: `${pageConfig.userPage}?redirect=${encodeURIComponent(path)}` })
uni.switchTab({ url: `${PAGE_CONFIG.USER_PAGE}?redirect=${encodeURIComponent(path)}` })
}
},
})
}
}
function http<T>(
method: 'GET' | 'POST' | 'UPLOAD_FILE' = 'POST',
url: string,
payload?: Payload | UploadFilePayload,
options: RequestOptions = {},
) {
options.header = { 'Content-Type': 'application/json', ...options.header }
const token = useUserStore().token
if (!options.ignoreToken && token) {
if (options.header) {
options.header['x-request-token'] = token
} else {
options.header = { 'x-request-token': token }
}
}
/* query string splice */
if (method === 'GET' && (payload as Payload)) {
const queryString = objectToDeepQueryString((payload as unknown as SerializableObject)!)
if (url.includes('.json?')) {
url += queryString
} else {
url += `?${queryString}`
}
} else if (method === 'POST' && (payload as Payload)?.params) {
const params = (payload as Payload)?.params || {}
const queryString = objectToDeepQueryString(params)
delete (payload as Payload)?.params
if (url.includes('.json?')) {
url += queryString
} else {
url += `?${queryString}`
}
}
/* 请求成功处理函数 */
const successHandler = (res: Response<T>) => {
if (!options.ignoreLoginHandle && res.code === -10) {
handleLogout()
return res
}
if (res.code === 0 || options.ignoreErrorCheck) return res
if (res.message) {
// const msg = res.message.match(/\[(?<msg>[\s\S]+)\]/)?.groups?.msg
uni.showToast({
title: res.message,
icon: 'none',
})
}
return Promise.resolve(res)
}
/* 请求失败处理函数 */
const failHandler = (error: Error) => {
throw new Error(error?.message || 'Network Error')
}
switch (method) {
case 'GET':
return service.get<T>(url, options).then(successHandler, failHandler)
case 'POST':
return service.post<T>(url, payload || null, options).then(successHandler, failHandler)
case 'UPLOAD_FILE':
return service.uploadFile<T>(url, (payload as UploadFilePayload)!, options).then(successHandler, failHandler)
}
}
export const request = {
get<T>(url: string, params?: Payload, options?: RequestOptions) {
return http<T>('GET', url, params, options)
},
post<T>(url: string, payload?: Payload, options?: RequestOptions) {
return http<T>('POST', url, payload, options)
},
uploadFile<T>(url: string, payload: UploadFilePayload, options?: RequestOptions) {
return http<T>('UPLOAD_FILE', url, payload, options)
},
}
import { VantComponent } from '../common/component';
import { button } from '../mixins/button';
VantComponent({
classes: ['list-class'],
mixins: [button],
props: {
show: Boolean,
title: String,
cancelText: String,
description: String,
round: {
type: Boolean,
value: true,
},
zIndex: {
type: Number,
value: 100,
},
actions: {
type: Array,
value: [],
},
overlay: {
type: Boolean,
value: true,
},
closeOnClickOverlay: {
type: Boolean,
value: true,
},
closeOnClickAction: {
type: Boolean,
value: true,
},
safeAreaInsetBottom: {
type: Boolean,
value: true,
},
rootPortal: {
type: Boolean,
value: false,
},
},
methods: {
onSelect(event) {
const { index } = event.currentTarget.dataset;
const { actions, closeOnClickAction, canIUseGetUserProfile } = this.data;
const item = actions[index];
if (item) {
this.$emit('select', item);
if (closeOnClickAction) {
this.onClose();
}
if (item.openType === 'getUserInfo' && canIUseGetUserProfile) {
wx.getUserProfile({
desc: item.getUserProfileDesc || ' ',
complete: (userProfile) => {
this.$emit('getuserinfo', userProfile);
},
});
}
}
},
onCancel() {
this.$emit('cancel');
},
onClose() {
this.$emit('close');
},
onClickOverlay() {
this.$emit('click-overlay');
this.onClose();
},
},
});
{
"component": true,
"usingComponents": {
"van-icon": "../icon/index",
"van-popup": "../popup/index",
"van-loading": "../loading/index"
}
}
<wxs src="../wxs/utils.wxs" module="utils" />
<van-popup
show="{{ show }}"
position="bottom"
round="{{ round }}"
z-index="{{ zIndex }}"
overlay="{{ overlay }}"
custom-class="van-action-sheet custom-class"
safe-area-inset-bottom="{{ safeAreaInsetBottom }}"
close-on-click-overlay="{{ closeOnClickOverlay }}"
root-portal="{{ rootPortal }}"
bind:close="onClickOverlay"
>
<view wx:if="{{ title }}" class="van-action-sheet__header">
{{ title }}
<van-icon
name="cross"
custom-class="van-action-sheet__close"
bind:click="onClose"
/>
</view>
<view wx:if="{{ description }}" class="van-action-sheet__description van-hairline--bottom">
{{ description }}
</view>
<view wx:if="{{ actions && actions.length }}" class="list-class">
<!-- button外包一层view,防止actions动态变化,导致渲染时button被打散 -->
<button
wx:for="{{ actions }}"
wx:key="index"
open-type="{{ item.disabled || item.loading || (canIUseGetUserProfile && item.openType === 'getUserInfo') ? '' : item.openType }}"
style="{{ item.color ? 'color: ' + item.color : '' }}"
class="{{ utils.bem('action-sheet__item', { disabled: item.disabled || item.loading }) }} {{ item.className || '' }}"
hover-class="van-action-sheet__item--hover"
data-index="{{ index }}"
bindtap="{{ item.disabled || item.loading ? '' : 'onSelect' }}"
bindgetuserinfo="onGetUserInfo"
bindcontact="onContact"
bindgetphonenumber="onGetPhoneNumber"
binderror="onError"
bindlaunchapp="onLaunchApp"
bindopensetting="onOpenSetting"
lang="{{ lang }}"
session-from="{{ sessionFrom }}"
send-message-title="{{ sendMessageTitle }}"
send-message-path="{{ sendMessagePath }}"
send-message-img="{{ sendMessageImg }}"
show-message-card="{{ showMessageCard }}"
app-parameter="{{ appParameter }}"
>
<block wx:if="{{ !item.loading }}">
{{ item.name }}
<view wx:if="{{ item.subname }}" class="van-action-sheet__subname" >{{ item.subname }}</view>
</block>
<van-loading wx:else custom-class="van-action-sheet__loading" size="22px" />
</button>
</view>
<slot />
<block wx:if="{{ cancelText }}">
<view class="van-action-sheet__gap" />
<view
class="van-action-sheet__cancel"
hover-class="van-action-sheet__cancel--hover"
hover-stay-time="70"
bind:tap="onCancel"
>
{{ cancelText }}
</view>
</block>
</van-popup>
@import '../common/index.wxss';.van-action-sheet{color:var(--action-sheet-item-text-color,#323233);max-height:var(--action-sheet-max-height,90%)!important}.van-action-sheet__cancel,.van-action-sheet__item{background-color:var(--action-sheet-item-background,#fff);font-size:var(--action-sheet-item-font-size,16px);line-height:var(--action-sheet-item-line-height,22px);padding:14px 16px;text-align:center}.van-action-sheet__cancel--hover,.van-action-sheet__item--hover{background-color:#f2f3f5}.van-action-sheet__cancel:after,.van-action-sheet__item:after{border-width:0}.van-action-sheet__cancel{color:var(--action-sheet-cancel-text-color,#646566)}.van-action-sheet__gap{background-color:var(--action-sheet-cancel-padding-color,#f7f8fa);display:block;height:var(--action-sheet-cancel-padding-top,8px)}.van-action-sheet__item--disabled{color:var(--action-sheet-item-disabled-text-color,#c8c9cc)}.van-action-sheet__item--disabled.van-action-sheet__item--hover{background-color:var(--action-sheet-item-background,#fff)}.van-action-sheet__subname{color:var(--action-sheet-subname-color,#969799);font-size:var(--action-sheet-subname-font-size,12px);line-height:var(--action-sheet-subname-line-height,20px);margin-top:var(--padding-xs,8px)}.van-action-sheet__header{font-size:var(--action-sheet-header-font-size,16px);font-weight:var(--font-weight-bold,500);line-height:var(--action-sheet-header-height,48px);text-align:center}.van-action-sheet__description{color:var(--action-sheet-description-color,#969799);font-size:var(--action-sheet-description-font-size,14px);line-height:var(--action-sheet-description-line-height,20px);padding:20px var(--padding-md,16px);text-align:center}.van-action-sheet__close{color:var(--action-sheet-close-icon-color,#c8c9cc);font-size:var(--action-sheet-close-icon-size,22px)!important;line-height:inherit!important;padding:var(--action-sheet-close-icon-padding,0 16px);position:absolute!important;right:0;top:0}.van-action-sheet__loading{display:flex!important}
\ No newline at end of file
import { VantComponent } from '../common/component';
import { pickerProps } from '../picker/shared';
import { requestAnimationFrame } from '../common/utils';
const EMPTY_CODE = '000000';
VantComponent({
classes: ['active-class', 'toolbar-class', 'column-class'],
props: Object.assign(Object.assign({}, pickerProps), { showToolbar: {
type: Boolean,
value: true,
}, value: {
type: String,
observer(value) {
this.code = value;
this.setValues();
},
}, areaList: {
type: Object,
value: {},
observer: 'setValues',
}, columnsNum: {
type: null,
value: 3,
}, columnsPlaceholder: {
type: Array,
observer(val) {
this.setData({
typeToColumnsPlaceholder: {
province: val[0] || '',
city: val[1] || '',
county: val[2] || '',
},
});
},
} }),
data: {
columns: [{ values: [] }, { values: [] }, { values: [] }],
typeToColumnsPlaceholder: {},
},
mounted() {
requestAnimationFrame(() => {
this.setValues();
});
},
methods: {
getPicker() {
if (this.picker == null) {
this.picker = this.selectComponent('.van-area__picker');
}
return this.picker;
},
onCancel(event) {
this.emit('cancel', event.detail);
},
onConfirm(event) {
const { index } = event.detail;
let { value } = event.detail;
value = this.parseValues(value);
this.emit('confirm', { value, index });
},
emit(type, detail) {
detail.values = detail.value;
delete detail.value;
this.$emit(type, detail);
},
parseValues(values) {
const { columnsPlaceholder } = this.data;
return values.map((value, index) => {
if (value &&
(!value.code || value.name === columnsPlaceholder[index])) {
return Object.assign(Object.assign({}, value), { code: '', name: '' });
}
return value;
});
},
onChange(event) {
var _a;
const { index, picker, value } = event.detail;
this.code = value[index].code;
(_a = this.setValues()) === null || _a === void 0 ? void 0 : _a.then(() => {
this.$emit('change', {
picker,
values: this.parseValues(picker.getValues()),
index,
});
});
},
getConfig(type) {
const { areaList } = this.data;
return (areaList && areaList[`${type}_list`]) || {};
},
getList(type, code) {
if (type !== 'province' && !code) {
return [];
}
const { typeToColumnsPlaceholder } = this.data;
const list = this.getConfig(type);
let result = Object.keys(list).map((code) => ({
code,
name: list[code],
}));
if (code != null) {
// oversea code
if (code[0] === '9' && type === 'city') {
code = '9';
}
result = result.filter((item) => item.code.indexOf(code) === 0);
}
if (typeToColumnsPlaceholder[type] && result.length) {
// set columns placeholder
const codeFill = type === 'province'
? ''
: type === 'city'
? EMPTY_CODE.slice(2, 4)
: EMPTY_CODE.slice(4, 6);
result.unshift({
code: `${code}${codeFill}`,
name: typeToColumnsPlaceholder[type],
});
}
return result;
},
getIndex(type, code) {
let compareNum = type === 'province' ? 2 : type === 'city' ? 4 : 6;
const list = this.getList(type, code.slice(0, compareNum - 2));
// oversea code
if (code[0] === '9' && type === 'province') {
compareNum = 1;
}
code = code.slice(0, compareNum);
for (let i = 0; i < list.length; i++) {
if (list[i].code.slice(0, compareNum) === code) {
return i;
}
}
return 0;
},
setValues() {
const picker = this.getPicker();
if (!picker) {
return;
}
let code = this.code || this.getDefaultCode();
const provinceList = this.getList('province');
const cityList = this.getList('city', code.slice(0, 2));
const stack = [];
const indexes = [];
const { columnsNum } = this.data;
if (columnsNum >= 1) {
stack.push(picker.setColumnValues(0, provinceList, false));
indexes.push(this.getIndex('province', code));
}
if (columnsNum >= 2) {
stack.push(picker.setColumnValues(1, cityList, false));
indexes.push(this.getIndex('city', code));
if (cityList.length && code.slice(2, 4) === '00') {
[{ code }] = cityList;
}
}
if (columnsNum === 3) {
stack.push(picker.setColumnValues(2, this.getList('county', code.slice(0, 4)), false));
indexes.push(this.getIndex('county', code));
}
return Promise.all(stack)
.catch(() => { })
.then(() => picker.setIndexes(indexes))
.catch(() => { });
},
getDefaultCode() {
const { columnsPlaceholder } = this.data;
if (columnsPlaceholder.length) {
return EMPTY_CODE;
}
const countyCodes = Object.keys(this.getConfig('county'));
if (countyCodes[0]) {
return countyCodes[0];
}
const cityCodes = Object.keys(this.getConfig('city'));
if (cityCodes[0]) {
return cityCodes[0];
}
return '';
},
getValues() {
const picker = this.getPicker();
if (!picker) {
return [];
}
return this.parseValues(picker.getValues().filter((value) => !!value));
},
getDetail() {
const values = this.getValues();
const area = {
code: '',
country: '',
province: '',
city: '',
county: '',
};
if (!values.length) {
return area;
}
const names = values.map((item) => item.name);
area.code = values[values.length - 1].code;
if (area.code[0] === '9') {
area.country = names[1] || '';
area.province = names[2] || '';
}
else {
area.province = names[0] || '';
area.city = names[1] || '';
area.county = names[2] || '';
}
return area;
},
reset(code) {
this.code = code || '';
return this.setValues();
},
},
});
{
"component": true,
"usingComponents": {
"van-picker": "../picker/index"
}
}
<wxs src="./index.wxs" module="computed" />
<van-picker
class="van-area__picker"
active-class="active-class"
toolbar-class="toolbar-class"
column-class="column-class"
show-toolbar="{{ showToolbar }}"
value-key="name"
title="{{ title }}"
loading="{{ loading }}"
columns="{{ computed.displayColumns(columns, columnsNum) }}"
item-height="{{ itemHeight }}"
visible-item-count="{{ visibleItemCount }}"
cancel-button-text="{{ cancelButtonText }}"
confirm-button-text="{{ confirmButtonText }}"
bind:change="onChange"
bind:confirm="onConfirm"
bind:cancel="onCancel"
/>
/* eslint-disable */
function displayColumns(columns, columnsNum) {
return columns.slice(0, +columnsNum);
}
module.exports = {
displayColumns: displayColumns,
};
@import '../common/index.wxss';
\ No newline at end of file
import { VantComponent } from '../common/component';
import { button } from '../mixins/button';
import { canIUseFormFieldButton } from '../common/version';
const mixins = [button];
if (canIUseFormFieldButton()) {
mixins.push('wx://form-field-button');
}
VantComponent({
mixins,
classes: ['hover-class', 'loading-class'],
data: {
baseStyle: '',
},
props: {
formType: String,
icon: String,
classPrefix: {
type: String,
value: 'van-icon',
},
plain: Boolean,
block: Boolean,
round: Boolean,
square: Boolean,
loading: Boolean,
hairline: Boolean,
disabled: Boolean,
loadingText: String,
customStyle: String,
loadingType: {
type: String,
value: 'circular',
},
type: {
type: String,
value: 'default',
},
dataset: null,
size: {
type: String,
value: 'normal',
},
loadingSize: {
type: String,
value: '20px',
},
color: String,
},
methods: {
onClick(event) {
this.$emit('click', event);
const { canIUseGetUserProfile, openType, getUserProfileDesc, lang, } = this.data;
if (openType === 'getUserInfo' && canIUseGetUserProfile) {
wx.getUserProfile({
desc: getUserProfileDesc || ' ',
lang: lang || 'en',
complete: (userProfile) => {
this.$emit('getuserinfo', userProfile);
},
});
}
},
},
});
{
"component": true,
"usingComponents": {
"van-icon": "../icon/index",
"van-loading": "../loading/index"
}
}
<wxs src="../wxs/utils.wxs" module="utils" />
<wxs src="./index.wxs" module="computed" />
<button
id="{{ id || buttonId }}"
data-detail="{{ dataset }}"
class="custom-class {{ utils.bem('button', [type, size, { block, round, plain, square, loading, disabled, hairline, unclickable: disabled || loading }]) }} {{ hairline ? 'van-hairline--surround' : '' }}"
hover-class="{{ disabled || loading ? '' : 'van-button--active hover-class'}}"
lang="{{ lang }}"
form-type="{{ formType }}"
style="{{ computed.rootStyle({ plain, color, customStyle }) }}"
open-type="{{ disabled || loading || (canIUseGetUserProfile && openType === 'getUserInfo') ? '' : openType }}"
business-id="{{ businessId }}"
session-from="{{ sessionFrom }}"
send-message-title="{{ sendMessageTitle }}"
send-message-path="{{ sendMessagePath }}"
send-message-img="{{ sendMessageImg }}"
show-message-card="{{ showMessageCard }}"
app-parameter="{{ appParameter }}"
aria-label="{{ ariaLabel }}"
bindtap="{{ disabled || loading ? '' : 'onClick' }}"
bindgetuserinfo="onGetUserInfo"
bindcontact="onContact"
bindgetphonenumber="onGetPhoneNumber"
bindgetrealtimephonenumber="onGetRealTimePhoneNumber"
bindagreeprivacyauthorization="onAgreePrivacyAuthorization"
binderror="onError"
bindlaunchapp="onLaunchApp"
bindopensetting="onOpenSetting"
bindchooseavatar="onChooseAvatar"
>
<block wx:if="{{ loading }}">
<van-loading
custom-class="loading-class"
size="{{ loadingSize }}"
type="{{ loadingType }}"
color="{{ computed.loadingColor({ type, color, plain }) }}"
/>
<view wx:if="{{ loadingText }}" class="van-button__loading-text">
{{ loadingText }}
</view>
</block>
<block wx:else>
<van-icon
wx:if="{{ icon }}"
size="1.2em"
name="{{ icon }}"
class-prefix="{{ classPrefix }}"
class="van-button__icon"
custom-style="line-height: inherit;"
/>
<view class="van-button__text">
<slot />
</view>
</block>
</button>
/* eslint-disable */
var style = require('../wxs/style.wxs');
function rootStyle(data) {
if (!data.color) {
return data.customStyle;
}
var properties = {
color: data.plain ? data.color : '#fff',
background: data.plain ? null : data.color,
};
// hide border when color is linear-gradient
if (data.color.indexOf('gradient') !== -1) {
properties.border = 0;
} else {
properties['border-color'] = data.color;
}
return style([properties, data.customStyle]);
}
function loadingColor(data) {
if (data.plain) {
return data.color ? data.color : '#c9c9c9';
}
if (data.type === 'default') {
return '#c9c9c9';
}
return '#fff';
}
module.exports = {
rootStyle: rootStyle,
loadingColor: loadingColor,
};
@import '../common/index.wxss';.van-button{-webkit-text-size-adjust:100%;align-items:center;-webkit-appearance:none;border-radius:var(--button-border-radius,2px);box-sizing:border-box;display:inline-flex;font-size:var(--button-default-font-size,16px);height:var(--button-default-height,44px);justify-content:center;line-height:var(--button-line-height,20px);padding:0;position:relative;text-align:center;transition:opacity .2s;vertical-align:middle}.van-button:before{background-color:#000;border:inherit;border-color:#000;border-radius:inherit;content:" ";height:100%;left:50%;opacity:0;position:absolute;top:50%;transform:translate(-50%,-50%);width:100%}.van-button:after{border-width:0}.van-button--active:before{opacity:.15}.van-button--unclickable:after{display:none}.van-button--default{background:var(--button-default-background-color,#fff);border:var(--button-border-width,1px) solid var(--button-default-border-color,#ebedf0);color:var(--button-default-color,#323233)}.van-button--primary{background:var(--button-primary-background-color,#07c160);border:var(--button-border-width,1px) solid var(--button-primary-border-color,#07c160);color:var(--button-primary-color,#fff)}.van-button--info{background:var(--button-info-background-color,#1989fa);border:var(--button-border-width,1px) solid var(--button-info-border-color,#1989fa);color:var(--button-info-color,#fff)}.van-button--danger{background:var(--button-danger-background-color,#ee0a24);border:var(--button-border-width,1px) solid var(--button-danger-border-color,#ee0a24);color:var(--button-danger-color,#fff)}.van-button--warning{background:var(--button-warning-background-color,#ff976a);border:var(--button-border-width,1px) solid var(--button-warning-border-color,#ff976a);color:var(--button-warning-color,#fff)}.van-button--plain{background:var(--button-plain-background-color,#fff)}.van-button--plain.van-button--primary{color:var(--button-primary-background-color,#07c160)}.van-button--plain.van-button--info{color:var(--button-info-background-color,#1989fa)}.van-button--plain.van-button--danger{color:var(--button-danger-background-color,#ee0a24)}.van-button--plain.van-button--warning{color:var(--button-warning-background-color,#ff976a)}.van-button--large{height:var(--button-large-height,50px);width:100%}.van-button--normal{font-size:var(--button-normal-font-size,14px);padding:0 15px}.van-button--small{font-size:var(--button-small-font-size,12px);height:var(--button-small-height,30px);min-width:var(--button-small-min-width,60px);padding:0 var(--padding-xs,8px)}.van-button--mini{display:inline-block;font-size:var(--button-mini-font-size,10px);height:var(--button-mini-height,22px);min-width:var(--button-mini-min-width,50px)}.van-button--mini+.van-button--mini{margin-left:5px}.van-button--block{display:flex;width:100%}.van-button--round{border-radius:var(--button-round-border-radius,999px)}.van-button--square{border-radius:0}.van-button--disabled{opacity:var(--button-disabled-opacity,.5)}.van-button__text{display:inline}.van-button__icon+.van-button__text:not(:empty),.van-button__loading-text{margin-left:4px}.van-button__icon{line-height:inherit!important;min-width:1em;vertical-align:top}.van-button--hairline{border-width:0;padding-top:1px}.van-button--hairline:after{border-color:inherit;border-radius:calc(var(--button-border-radius, 2px)*2);border-width:1px}.van-button--hairline.van-button--round:after{border-radius:var(--button-round-border-radius,999px)}.van-button--hairline.van-button--square:after{border-radius:0}
\ No newline at end of file
<wxs src="./index.wxs" module="computed" />
<wxs src="../wxs/utils.wxs" module="utils" />
<view class="van-calendar">
<header
title="{{ title }}"
showTitle="{{ showTitle }}"
subtitle="{{ subtitle }}"
showSubtitle="{{ showSubtitle }}"
firstDayOfWeek="{{ firstDayOfWeek }}"
bind:click-subtitle="onClickSubtitle"
>
<slot name="title" slot="title"></slot>
</header>
<scroll-view
class="van-calendar__body"
scroll-y
scroll-into-view="{{ scrollIntoView }}"
>
<month
wx:for="{{ computed.getMonths(minDate, maxDate) }}"
wx:key="index"
id="month{{ index }}"
class="month"
data-date="{{ item }}"
date="{{ item }}"
type="{{ type }}"
color="{{ color }}"
minDate="{{ minDate }}"
maxDate="{{ maxDate }}"
showMark="{{ showMark }}"
formatter="{{ formatter }}"
rowHeight="{{ rowHeight }}"
currentDate="{{ currentDate }}"
showSubtitle="{{ showSubtitle }}"
allowSameDay="{{ allowSameDay }}"
showMonthTitle="{{ index !== 0 || !showSubtitle }}"
firstDayOfWeek="{{ firstDayOfWeek }}"
bind:click="onClickDay"
/>
</scroll-view>
<view
class="{{ utils.bem('calendar__footer', { safeAreaInsetBottom }) }}"
>
<slot name="footer"></slot>
</view>
<view
class="{{ utils.bem('calendar__footer', { safeAreaInsetBottom }) }}"
>
<van-button
wx:if="{{ showConfirm }}"
round
block
type="danger"
color="{{ color }}"
custom-class="van-calendar__confirm"
disabled="{{ computed.getButtonDisabled(type, currentDate, minRange) }}"
nativeType="text"
bind:click="onConfirm"
>
{{
computed.getButtonDisabled(type, currentDate, minRange)
? confirmDisabledText
: confirmText
}}
</van-button>
</view>
</view>
import { VantComponent } from '../../../common/component';
VantComponent({
props: {
title: {
type: String,
value: '日期选择',
},
subtitle: String,
showTitle: Boolean,
showSubtitle: Boolean,
firstDayOfWeek: {
type: Number,
observer: 'initWeekDay',
},
},
data: {
weekdays: [],
},
created() {
this.initWeekDay();
},
methods: {
initWeekDay() {
const defaultWeeks = ['日', '一', '二', '三', '四', '五', '六'];
const firstDayOfWeek = this.data.firstDayOfWeek || 0;
this.setData({
weekdays: [
...defaultWeeks.slice(firstDayOfWeek, 7),
...defaultWeeks.slice(0, firstDayOfWeek),
],
});
},
onClickSubtitle(event) {
this.$emit('click-subtitle', event);
},
},
});
<view class="van-calendar__header">
<block wx:if="{{ showTitle }}">
<view class="van-calendar__header-title"><slot name="title"></slot></view>
<view class="van-calendar__header-title">{{ title }}</view>
</block>
<view wx:if="{{ showSubtitle }}" class="van-calendar__header-subtitle" bind:tap="onClickSubtitle">
{{ subtitle }}
</view>
<view class="van-calendar__weekdays">
<view wx:for="{{ weekdays }}" wx:key="index" class="van-calendar__weekday">
{{ item }}
</view>
</view>
</view>
@import '../../../common/index.wxss';.van-calendar__header{box-shadow:var(--calendar-header-box-shadow,0 2px 10px hsla(220,1%,50%,.16));flex-shrink:0}.van-calendar__header-subtitle,.van-calendar__header-title{font-weight:var(--font-weight-bold,500);height:var(--calendar-header-title-height,44px);line-height:var(--calendar-header-title-height,44px);text-align:center}.van-calendar__header-title+.van-calendar__header-title,.van-calendar__header-title:empty{display:none}.van-calendar__header-title:empty+.van-calendar__header-title{display:block!important}.van-calendar__weekdays{display:flex}.van-calendar__weekday{flex:1;font-size:var(--calendar-weekdays-font-size,12px);line-height:var(--calendar-weekdays-height,30px);text-align:center}
\ No newline at end of file
export interface Day {
date: Date;
type: string;
text: number;
bottomInfo?: string;
}
import { VantComponent } from '../../../common/component';
import { getMonthEndDay, compareDay, getPrevDay, getNextDay, } from '../../utils';
VantComponent({
props: {
date: {
type: null,
observer: 'setDays',
},
type: {
type: String,
observer: 'setDays',
},
color: String,
minDate: {
type: null,
observer: 'setDays',
},
maxDate: {
type: null,
observer: 'setDays',
},
showMark: Boolean,
rowHeight: null,
formatter: {
type: null,
observer: 'setDays',
},
currentDate: {
type: null,
observer: 'setDays',
},
firstDayOfWeek: {
type: Number,
observer: 'setDays',
},
allowSameDay: Boolean,
showSubtitle: Boolean,
showMonthTitle: Boolean,
},
data: {
visible: true,
days: [],
},
methods: {
onClick(event) {
const { index } = event.currentTarget.dataset;
const item = this.data.days[index];
if (item.type !== 'disabled') {
this.$emit('click', item);
}
},
setDays() {
const days = [];
const startDate = new Date(this.data.date);
const year = startDate.getFullYear();
const month = startDate.getMonth();
const totalDay = getMonthEndDay(startDate.getFullYear(), startDate.getMonth() + 1);
for (let day = 1; day <= totalDay; day++) {
const date = new Date(year, month, day);
const type = this.getDayType(date);
let config = {
date,
type,
text: day,
bottomInfo: this.getBottomInfo(type),
};
if (this.data.formatter) {
config = this.data.formatter(config);
}
days.push(config);
}
this.setData({ days });
},
getMultipleDayType(day) {
const { currentDate } = this.data;
if (!Array.isArray(currentDate)) {
return '';
}
const isSelected = (date) => currentDate.some((item) => compareDay(item, date) === 0);
if (isSelected(day)) {
const prevDay = getPrevDay(day);
const nextDay = getNextDay(day);
const prevSelected = isSelected(prevDay);
const nextSelected = isSelected(nextDay);
if (prevSelected && nextSelected) {
return 'multiple-middle';
}
if (prevSelected) {
return 'end';
}
return nextSelected ? 'start' : 'multiple-selected';
}
return '';
},
getRangeDayType(day) {
const { currentDate, allowSameDay } = this.data;
if (!Array.isArray(currentDate)) {
return '';
}
const [startDay, endDay] = currentDate;
if (!startDay) {
return '';
}
const compareToStart = compareDay(day, startDay);
if (!endDay) {
return compareToStart === 0 ? 'start' : '';
}
const compareToEnd = compareDay(day, endDay);
if (compareToStart === 0 && compareToEnd === 0 && allowSameDay) {
return 'start-end';
}
if (compareToStart === 0) {
return 'start';
}
if (compareToEnd === 0) {
return 'end';
}
if (compareToStart > 0 && compareToEnd < 0) {
return 'middle';
}
return '';
},
getDayType(day) {
const { type, minDate, maxDate, currentDate } = this.data;
if (compareDay(day, minDate) < 0 || compareDay(day, maxDate) > 0) {
return 'disabled';
}
if (type === 'single') {
return compareDay(day, currentDate) === 0 ? 'selected' : '';
}
if (type === 'multiple') {
return this.getMultipleDayType(day);
}
/* istanbul ignore else */
if (type === 'range') {
return this.getRangeDayType(day);
}
return '';
},
getBottomInfo(type) {
if (this.data.type === 'range') {
if (type === 'start') {
return '开始';
}
if (type === 'end') {
return '结束';
}
if (type === 'start-end') {
return '开始/结束';
}
}
},
},
});
<wxs src="./index.wxs" module="computed"></wxs>
<wxs src="../../../wxs/utils.wxs" module="utils" />
<view class="van-calendar__month" style="{{ computed.getMonthStyle(visible, date, rowHeight) }}">
<view wx:if="{{ showMonthTitle }}" class="van-calendar__month-title">
{{ computed.formatMonthTitle(date) }}
</view>
<view wx:if="{{ visible }}" class="van-calendar__days">
<view wx:if="{{ showMark }}" class="van-calendar__month-mark">
{{ computed.getMark(date) }}
</view>
<view
wx:for="{{ days }}"
wx:key="index"
style="{{ computed.getDayStyle(item.type, index, date, rowHeight, color, firstDayOfWeek) }}"
class="{{ utils.bem('calendar__day', [item.type]) }} {{ item.className }}"
data-index="{{ index }}"
bindtap="onClick"
>
<view wx:if="{{ item.type === 'selected' }}" class="van-calendar__selected-day" style="width: {{ rowHeight }}px; height: {{ rowHeight }}px; background: {{ color }}">
<view wx:if="{{ item.topInfo }}" class="van-calendar__top-info">{{ item.topInfo }}</view>
{{ item.text }}
<view wx:if="{{ item.bottomInfo }}" class="van-calendar__bottom-info">
{{ item.bottomInfo }}
</view>
</view>
<view wx:else>
<view wx:if="{{ item.topInfo }}" class="van-calendar__top-info">{{ item.topInfo }}</view>
{{ item.text }}
<view wx:if="{{ item.bottomInfo }}" class="van-calendar__bottom-info">
{{ item.bottomInfo }}
</view>
</view>
</view>
</view>
</view>
/* eslint-disable */
var utils = require('../../utils.wxs');
function getMark(date) {
return getDate(date).getMonth() + 1;
}
var ROW_HEIGHT = 64;
function getDayStyle(type, index, date, rowHeight, color, firstDayOfWeek) {
var style = [];
var current = getDate(date).getDay() || 7;
var offset = current < firstDayOfWeek ? (7 - firstDayOfWeek + current) :
current === 7 && firstDayOfWeek === 0 ? 0 :
(current - firstDayOfWeek);
if (index === 0) {
style.push(['margin-left', (100 * offset) / 7 + '%']);
}
if (rowHeight !== ROW_HEIGHT) {
style.push(['height', rowHeight + 'px']);
}
if (color) {
if (
type === 'start' ||
type === 'end' ||
type === 'start-end' ||
type === 'multiple-selected' ||
type === 'multiple-middle'
) {
style.push(['background', color]);
} else if (type === 'middle') {
style.push(['color', color]);
}
}
return style
.map(function(item) {
return item.join(':');
})
.join(';');
}
function formatMonthTitle(date) {
date = getDate(date);
return date.getFullYear() + '年' + (date.getMonth() + 1) + '月';
}
function getMonthStyle(visible, date, rowHeight) {
if (!visible) {
date = getDate(date);
var totalDay = utils.getMonthEndDay(
date.getFullYear(),
date.getMonth() + 1
);
var offset = getDate(date).getDay();
var padding = Math.ceil((totalDay + offset) / 7) * rowHeight;
return 'padding-bottom:' + padding + 'px';
}
}
module.exports = {
getMark: getMark,
getDayStyle: getDayStyle,
formatMonthTitle: formatMonthTitle,
getMonthStyle: getMonthStyle
};
@import '../../../common/index.wxss';.van-calendar{background-color:var(--calendar-background-color,#fff);display:flex;flex-direction:column;height:100%}.van-calendar__month-title{font-size:var(--calendar-month-title-font-size,14px);font-weight:var(--font-weight-bold,500);height:var(--calendar-header-title-height,44px);line-height:var(--calendar-header-title-height,44px);text-align:center}.van-calendar__days{display:flex;flex-wrap:wrap;position:relative;-webkit-user-select:none;user-select:none}.van-calendar__month-mark{color:var(--calendar-month-mark-color,rgba(242,243,245,.8));font-size:var(--calendar-month-mark-font-size,160px);left:50%;pointer-events:none;position:absolute;top:50%;transform:translate(-50%,-50%);z-index:0}.van-calendar__day,.van-calendar__selected-day{align-items:center;display:flex;justify-content:center;text-align:center}.van-calendar__day{font-size:var(--calendar-day-font-size,16px);height:var(--calendar-day-height,64px);position:relative;width:14.285%}.van-calendar__day--end,.van-calendar__day--multiple-middle,.van-calendar__day--multiple-selected,.van-calendar__day--start,.van-calendar__day--start-end{background-color:var(--calendar-range-edge-background-color,#ee0a24);color:var(--calendar-range-edge-color,#fff)}.van-calendar__day--start{border-radius:4px 0 0 4px}.van-calendar__day--end{border-radius:0 4px 4px 0}.van-calendar__day--multiple-selected,.van-calendar__day--start-end{border-radius:4px}.van-calendar__day--middle{color:var(--calendar-range-middle-color,#ee0a24)}.van-calendar__day--middle:after{background-color:currentColor;bottom:0;content:"";left:0;opacity:var(--calendar-range-middle-background-opacity,.1);position:absolute;right:0;top:0}.van-calendar__day--disabled{color:var(--calendar-day-disabled-color,#c8c9cc);cursor:default}.van-calendar__bottom-info,.van-calendar__top-info{font-size:var(--calendar-info-font-size,10px);left:0;line-height:var(--calendar-info-line-height,14px);position:absolute;right:0}@media (max-width:350px){.van-calendar__bottom-info,.van-calendar__top-info{font-size:9px}}.van-calendar__top-info{top:6px}.van-calendar__bottom-info{bottom:6px}.van-calendar__selected-day{background-color:var(--calendar-selected-day-background-color,#ee0a24);border-radius:4px;color:var(--calendar-selected-day-color,#fff);height:var(--calendar-selected-day-size,54px);width:var(--calendar-selected-day-size,54px)}
\ No newline at end of file
This diff is collapsed.
{
"component": true,
"usingComponents": {
"header": "./components/header/index",
"month": "./components/month/index",
"van-button": "../button/index",
"van-popup": "../popup/index",
"van-toast": "../toast/index"
}
}
<wxs src="./index.wxs" module="computed" />
<wxs src="../wxs/utils.wxs" module="utils" />
<import src="./calendar.wxml" />
<van-popup
wx:if="{{ poppable }}"
custom-class="van-calendar__popup--{{ position }}"
close-icon-class="van-calendar__close-icon"
show="{{ show }}"
round="{{ round }}"
position="{{ position }}"
closeable="{{ showTitle || showSubtitle }}"
close-on-click-overlay="{{ closeOnClickOverlay }}"
safe-area-inset-bottom="{{ safeAreaInsetBottom }}"
root-portal="{{ rootPortal }}"
bind:enter="onOpen"
bind:close="onClose"
bind:after-enter="onOpened"
bind:after-leave="onClosed"
>
<include src="./calendar.wxml" />
</van-popup>
<include wx:else src="./calendar.wxml" />
<van-toast id="van-toast" />
/* eslint-disable */
var utils = require('./utils.wxs');
function getMonths(minDate, maxDate) {
var months = [];
var cursor = getDate(minDate);
cursor.setDate(1);
do {
months.push(cursor.getTime());
cursor.setMonth(cursor.getMonth() + 1);
} while (utils.compareMonth(cursor, getDate(maxDate)) !== 1);
return months;
}
function getButtonDisabled(type, currentDate, minRange) {
if (currentDate == null) {
return true;
}
if (type === 'range') {
return !currentDate[0] || !currentDate[1];
}
if (type === 'multiple') {
return currentDate.length < minRange;
}
return !currentDate;
}
module.exports = {
getMonths: getMonths,
getButtonDisabled: getButtonDisabled
};
@import '../common/index.wxss';.van-calendar{background-color:var(--calendar-background-color,#fff);display:flex;flex-direction:column;height:var(--calendar-height,100%)}.van-calendar__close-icon{top:11px}.van-calendar__popup--bottom,.van-calendar__popup--top{height:var(--calendar-popup-height,90%)}.van-calendar__popup--left,.van-calendar__popup--right{height:100%}.van-calendar__body{-webkit-overflow-scrolling:touch;flex:1;overflow:auto}.van-calendar__footer{flex-shrink:0;padding:0 var(--padding-md,16px)}.van-calendar__footer--safe-area-inset-bottom{padding-bottom:env(safe-area-inset-bottom)}.van-calendar__footer+.van-calendar__footer,.van-calendar__footer:empty{display:none}.van-calendar__footer:empty+.van-calendar__footer{display:block!important}.van-calendar__confirm{height:var(--calendar-confirm-button-height,36px)!important;line-height:var(--calendar-confirm-button-line-height,34px)!important;margin:var(--calendar-confirm-button-margin,7px 0)!important}
\ No newline at end of file
export declare const ROW_HEIGHT = 64;
export declare function formatMonthTitle(date: Date): string;
export declare function compareMonth(date1: Date | number, date2: Date | number): 0 | 1 | -1;
export declare function compareDay(day1: Date | number, day2: Date | number): 0 | 1 | -1;
export declare function getDayByOffset(date: Date, offset: number): Date;
export declare function getPrevDay(date: Date): Date;
export declare function getNextDay(date: Date): Date;
export declare function getToday(): Date;
export declare function calcDateNum(date: [Date, Date]): number;
export declare function copyDates(dates: Date | Date[]): Date | Date[];
export declare function getMonthEndDay(year: number, month: number): number;
export declare function getMonths(minDate: number, maxDate: number): number[];
export const ROW_HEIGHT = 64;
export function formatMonthTitle(date) {
if (!(date instanceof Date)) {
date = new Date(date);
}
return `${date.getFullYear()}${date.getMonth() + 1}月`;
}
export function compareMonth(date1, date2) {
if (!(date1 instanceof Date)) {
date1 = new Date(date1);
}
if (!(date2 instanceof Date)) {
date2 = new Date(date2);
}
const year1 = date1.getFullYear();
const year2 = date2.getFullYear();
const month1 = date1.getMonth();
const month2 = date2.getMonth();
if (year1 === year2) {
return month1 === month2 ? 0 : month1 > month2 ? 1 : -1;
}
return year1 > year2 ? 1 : -1;
}
export function compareDay(day1, day2) {
if (!(day1 instanceof Date)) {
day1 = new Date(day1);
}
if (!(day2 instanceof Date)) {
day2 = new Date(day2);
}
const compareMonthResult = compareMonth(day1, day2);
if (compareMonthResult === 0) {
const date1 = day1.getDate();
const date2 = day2.getDate();
return date1 === date2 ? 0 : date1 > date2 ? 1 : -1;
}
return compareMonthResult;
}
export function getDayByOffset(date, offset) {
date = new Date(date);
date.setDate(date.getDate() + offset);
return date;
}
export function getPrevDay(date) {
return getDayByOffset(date, -1);
}
export function getNextDay(date) {
return getDayByOffset(date, 1);
}
export function getToday() {
const today = new Date();
today.setHours(0, 0, 0, 0);
return today;
}
export function calcDateNum(date) {
const day1 = new Date(date[0]).getTime();
const day2 = new Date(date[1]).getTime();
return (day2 - day1) / (1000 * 60 * 60 * 24) + 1;
}
export function copyDates(dates) {
if (Array.isArray(dates)) {
return dates.map((date) => {
if (date === null) {
return date;
}
return new Date(date);
});
}
return new Date(dates);
}
export function getMonthEndDay(year, month) {
return 32 - new Date(year, month - 1, 32).getDate();
}
export function getMonths(minDate, maxDate) {
const months = [];
const cursor = new Date(minDate);
cursor.setDate(1);
do {
months.push(cursor.getTime());
cursor.setMonth(cursor.getMonth() + 1);
} while (compareMonth(cursor, maxDate) !== 1);
return months;
}
/* eslint-disable */
function getMonthEndDay(year, month) {
return 32 - getDate(year, month - 1, 32).getDate();
}
function compareMonth(date1, date2) {
date1 = getDate(date1);
date2 = getDate(date2);
var year1 = date1.getFullYear();
var year2 = date2.getFullYear();
var month1 = date1.getMonth();
var month2 = date2.getMonth();
if (year1 === year2) {
return month1 === month2 ? 0 : month1 > month2 ? 1 : -1;
}
return year1 > year2 ? 1 : -1;
}
module.exports = {
getMonthEndDay: getMonthEndDay,
compareMonth: compareMonth
};
import { link } from '../mixins/link';
import { VantComponent } from '../common/component';
VantComponent({
classes: [
'num-class',
'desc-class',
'thumb-class',
'title-class',
'price-class',
'origin-price-class',
],
mixins: [link],
props: {
tag: String,
num: String,
desc: String,
thumb: String,
title: String,
price: {
type: String,
observer: 'updatePrice',
},
centered: Boolean,
lazyLoad: Boolean,
thumbLink: String,
originPrice: String,
thumbMode: {
type: String,
value: 'aspectFit',
},
currency: {
type: String,
value: '¥',
},
},
methods: {
updatePrice() {
const { price } = this.data;
const priceArr = price.toString().split('.');
this.setData({
integerStr: priceArr[0],
decimalStr: priceArr[1] ? `.${priceArr[1]}` : '',
});
},
onClickThumb() {
this.jumpLink('thumbLink');
},
},
});
{
"component": true,
"usingComponents": {
"van-tag": "../tag/index"
}
}
<wxs src="../wxs/utils.wxs" module="utils" />
<view class="custom-class van-card">
<view class="{{ utils.bem('card__header', { center: centered }) }}">
<view class="van-card__thumb" bind:tap="onClickThumb">
<image
wx:if="{{ thumb }}"
src="{{ thumb }}"
mode="{{ thumbMode }}"
lazy-load="{{ lazyLoad }}"
class="van-card__img thumb-class"
/>
<slot wx:else name="thumb" />
<van-tag
wx:if="{{ tag }}"
mark
type="danger"
custom-class="van-card__tag"
>
{{ tag }}
</van-tag>
<slot wx:else name="tag" />
</view>
<view class="van-card__content {{ utils.bem('card__content', { center: centered }) }}">
<view>
<view wx:if="{{ title }}" class="van-card__title title-class">{{ title }}</view>
<slot wx:else name="title" />
<view wx:if="{{ desc }}" class="van-card__desc desc-class">{{ desc }}</view>
<slot wx:else name="desc" />
<slot name="tags" />
</view>
<view class="van-card__bottom">
<slot name="price-top" />
<view wx:if="{{ price || price === 0 }}" class="van-card__price price-class">
<text>{{ currency }}</text>
<text class="van-card__price-integer">{{ integerStr }}</text>
<text class="van-card__price-decimal">{{ decimalStr }}</text>
</view>
<slot wx:else name="price" />
<view wx:if="{{ originPrice || originPrice === 0 }}" class="van-card__origin-price origin-price-class">{{ currency }} {{ originPrice }}</view>
<slot wx:else name="origin-price" />
<view wx:if="{{ num }}" class="van-card__num num-class">x {{ num }}</view>
<slot wx:else name="num" />
<slot name="bottom" />
</view>
</view>
</view>
<view class="van-card__footer">
<slot name="footer" />
</view>
</view>
@import '../common/index.wxss';.van-card{background-color:var(--card-background-color,#fafafa);box-sizing:border-box;color:var(--card-text-color,#323233);font-size:var(--card-font-size,12px);padding:var(--card-padding,8px 16px);position:relative}.van-card__header{display:flex}.van-card__header--center{align-items:center;justify-content:center}.van-card__thumb{flex:none;height:var(--card-thumb-size,88px);margin-right:var(--padding-xs,8px);position:relative;width:var(--card-thumb-size,88px)}.van-card__thumb:empty{display:none}.van-card__img{border-radius:8px;height:100%;width:100%}.van-card__content{display:flex;flex:1;flex-direction:column;justify-content:space-between;min-height:var(--card-thumb-size,88px);min-width:0;position:relative}.van-card__content--center{justify-content:center}.van-card__desc,.van-card__title{word-wrap:break-word}.van-card__title{font-weight:700;line-height:var(--card-title-line-height,16px)}.van-card__desc{color:var(--card-desc-color,#646566);line-height:var(--card-desc-line-height,20px)}.van-card__bottom{line-height:20px}.van-card__price{color:var(--card-price-color,#ee0a24);display:inline-block;font-size:var(--card-price-font-size,12px);font-weight:700}.van-card__price-integer{font-size:var(--card-price-integer-font-size,16px)}.van-card__price-decimal,.van-card__price-integer{font-family:var(--card-price-font-family,Avenir-Heavy,PingFang SC,Helvetica Neue,Arial,sans-serif)}.van-card__origin-price{color:var(--card-origin-price-color,#646566);display:inline-block;font-size:var(--card-origin-price-font-size,10px);margin-left:5px;text-decoration:line-through}.van-card__num{float:right}.van-card__tag{left:0;position:absolute!important;top:2px}.van-card__footer{flex:none;text-align:right;width:100%}
\ No newline at end of file
import { VantComponent } from '../common/component';
var FieldName;
(function (FieldName) {
FieldName["TEXT"] = "text";
FieldName["VALUE"] = "value";
FieldName["CHILDREN"] = "children";
})(FieldName || (FieldName = {}));
const defaultFieldNames = {
text: FieldName.TEXT,
value: FieldName.VALUE,
children: FieldName.CHILDREN,
};
VantComponent({
props: {
title: String,
value: {
type: String,
},
placeholder: {
type: String,
value: '请选择',
},
activeColor: {
type: String,
value: '#1989fa',
},
options: {
type: Array,
value: [],
},
swipeable: {
type: Boolean,
value: false,
},
closeable: {
type: Boolean,
value: true,
},
ellipsis: {
type: Boolean,
value: true,
},
showHeader: {
type: Boolean,
value: true,
},
closeIcon: {
type: String,
value: 'cross',
},
fieldNames: {
type: Object,
value: defaultFieldNames,
observer: 'updateFieldNames',
},
useTitleSlot: Boolean,
},
data: {
tabs: [],
activeTab: 0,
textKey: FieldName.TEXT,
valueKey: FieldName.VALUE,
childrenKey: FieldName.CHILDREN,
innerValue: '',
},
watch: {
options() {
this.updateTabs();
},
value(newVal) {
this.updateValue(newVal);
},
},
created() {
this.updateTabs();
},
methods: {
updateValue(val) {
if (val !== undefined) {
const values = this.data.tabs.map((tab) => tab.selected && tab.selected[this.data.valueKey]);
if (values.indexOf(val) > -1) {
return;
}
}
this.innerValue = val;
this.updateTabs();
},
updateFieldNames() {
const { text = 'text', value = 'value', children = 'children', } = this.data.fieldNames || defaultFieldNames;
this.setData({
textKey: text,
valueKey: value,
childrenKey: children,
});
},
getSelectedOptionsByValue(options, value) {
for (let i = 0; i < options.length; i++) {
const option = options[i];
if (option[this.data.valueKey] === value) {
return [option];
}
if (option[this.data.childrenKey]) {
const selectedOptions = this.getSelectedOptionsByValue(option[this.data.childrenKey], value);
if (selectedOptions) {
return [option, ...selectedOptions];
}
}
}
},
updateTabs() {
const { options } = this.data;
const { innerValue } = this;
if (!options.length) {
return;
}
if (innerValue !== undefined) {
const selectedOptions = this.getSelectedOptionsByValue(options, innerValue);
if (selectedOptions) {
let optionsCursor = options;
const tabs = selectedOptions.map((option) => {
const tab = {
options: optionsCursor,
selected: option,
};
const next = optionsCursor.find((item) => item[this.data.valueKey] === option[this.data.valueKey]);
if (next) {
optionsCursor = next[this.data.childrenKey];
}
return tab;
});
if (optionsCursor) {
tabs.push({
options: optionsCursor,
selected: null,
});
}
this.setData({
tabs,
});
wx.nextTick(() => {
this.setData({
activeTab: tabs.length - 1,
});
});
return;
}
}
this.setData({
tabs: [
{
options,
selected: null,
},
],
activeTab: 0,
});
},
onClose() {
this.$emit('close');
},
onClickTab(e) {
const { index: tabIndex, title } = e.detail;
this.$emit('click-tab', { title, tabIndex });
this.setData({
activeTab: tabIndex,
});
},
// 选中
onSelect(e) {
const { option, tabIndex } = e.currentTarget.dataset;
if (option && option.disabled) {
return;
}
const { valueKey, childrenKey } = this.data;
let { tabs } = this.data;
tabs[tabIndex].selected = option;
if (tabs.length > tabIndex + 1) {
tabs = tabs.slice(0, tabIndex + 1);
}
if (option[childrenKey]) {
const nextTab = {
options: option[childrenKey],
selected: null,
};
if (tabs[tabIndex + 1]) {
tabs[tabIndex + 1] = nextTab;
}
else {
tabs.push(nextTab);
}
wx.nextTick(() => {
this.setData({
activeTab: tabIndex + 1,
});
});
}
this.setData({
tabs,
});
const selectedOptions = tabs.map((tab) => tab.selected).filter(Boolean);
const value = option[valueKey];
const params = {
value,
tabIndex,
selectedOptions,
};
this.innerValue = value;
this.$emit('change', params);
if (!option[childrenKey]) {
this.$emit('finish', params);
}
},
},
});
{
"component": true,
"usingComponents": {
"van-icon": "../icon/index",
"van-tab": "../tab/index",
"van-tabs": "../tabs/index"
}
}
\ No newline at end of file
<wxs src="./index.wxs" module="utils" />
<view wx:if="{{ showHeader }}" class="van-cascader__header">
<slot name="title" wx:if="{{ useTitleSlot }}"></slot>
<text class="van-cascader__title" wx:else>{{ title }}</text>
<van-icon
wx:if="{{ closeable }}"
name="{{ closeIcon }}"
class="van-cascader__close-icon"
bind:tap="onClose"
/>
</view>
<van-tabs
active="{{ activeTab }}"
custom-class="van-cascader__tabs"
wrap-class="van-cascader__tabs-wrap"
tab-class="van-cascader__tab"
color="{{ activeColor }}"
border="{{ false }}"
swipeable="{{ swipeable }}"
ellipsis="{{ ellipsis }}"
bind:click="onClickTab"
>
<van-tab
wx:for="{{ tabs }}"
wx:for-item="tab"
wx:for-index="tabIndex"
wx:key="tabIndex"
title="{{ tab.selected ? tab.selected[textKey] : placeholder }}"
style="width: 100%;"
title-style="{{ !tab.selected ? 'color: #969799;font-weight:normal;' : '' }}"
>
<!-- 暂不支持 -->
<!-- <slot name="options-top"></slot> -->
<view class="van-cascader__options">
<view
wx:for="{{ tab.options }}"
wx:for-item="option"
wx:key="index"
class="{{ option.className }} {{ utils.optionClass(tab, valueKey, option) }}"
style="{{ utils.optionStyle({ tab, valueKey, option, activeColor }) }}"
data-option="{{ option }}"
data-tab-index="{{ tabIndex }}"
bind:tap="onSelect"
>
<text>{{ option[textKey] }}</text>
<van-icon wx:if="{{ utils.isSelected(tab, valueKey, option) }}" name="success" size="18" />
</view>
</view>
<!-- 暂不支持 -->
<!-- <slot name="options-bottom"></slot> -->
</van-tab>
</van-tabs>
var utils = require('../wxs/utils.wxs');
var style = require('../wxs/style.wxs');
function isSelected(tab, valueKey, option) {
return tab.selected && tab.selected[valueKey] === option[valueKey]
}
function optionClass(tab, valueKey, option) {
return utils.bem('cascader__option', { selected: isSelected(tab, valueKey, option), disabled: option.disabled })
}
function optionStyle(data) {
var color = data.option.color || (isSelected(data.tab, data.valueKey, data.option) ? data.activeColor : undefined);
return style({
color
});
}
module.exports = {
isSelected: isSelected,
optionClass: optionClass,
optionStyle: optionStyle,
};
\ No newline at end of file
@import '../common/index.wxss';.van-cascader__header{align-items:center;display:flex;height:48px;justify-content:space-between;padding:0 16px}.van-cascader__title{font-size:16px;font-weight:600;line-height:20px}.van-cascader__close-icon{color:#c8c9cc;font-size:22px;height:22px}.van-cascader__tabs-wrap{height:48px!important;padding:0 8px}.van-cascader__tab{color:#323233!important;flex:none!important;font-weight:600!important;padding:0 8px!important}.van-cascader__tab--unselected{color:#969799!important;font-weight:400!important}.van-cascader__option{align-items:center;cursor:pointer;display:flex;font-size:14px;justify-content:space-between;line-height:20px;padding:10px 16px}.van-cascader__option:active{background-color:#f2f3f5}.van-cascader__option--selected{color:#1989fa;font-weight:600}.van-cascader__option--disabled{color:#c8c9cc;cursor:not-allowed}.van-cascader__option--disabled:active{background-color:initial}.van-cascader__options{-webkit-overflow-scrolling:touch;box-sizing:border-box;height:384px;overflow-y:auto;padding-top:6px}
\ No newline at end of file
import { VantComponent } from '../common/component';
VantComponent({
props: {
title: String,
border: {
type: Boolean,
value: true,
},
inset: Boolean,
},
});
<wxs src="../wxs/utils.wxs" module="utils" />
<view
wx:if="{{ title }}"
class="{{ utils.bem('cell-group__title', { inset }) }}"
>
{{ title }}
</view>
<view class="custom-class {{ utils.bem('cell-group', { inset }) }} {{ border ? 'van-hairline--top-bottom' : '' }}">
<slot />
</view>
@import '../common/index.wxss';.van-cell-group--inset{border-radius:var(--cell-group-inset-border-radius,8px);margin:var(--cell-group-inset-padding,0 16px);overflow:hidden}.van-cell-group__title{color:var(--cell-group-title-color,#969799);font-size:var(--cell-group-title-font-size,14px);line-height:var(--cell-group-title-line-height,16px);padding:var(--cell-group-title-padding,16px 16px 8px)}.van-cell-group__title--inset{padding:var(--cell-group-inset-title-padding,16px 16px 8px 32px)}
\ No newline at end of file
import { link } from '../mixins/link';
import { VantComponent } from '../common/component';
VantComponent({
classes: [
'title-class',
'label-class',
'value-class',
'right-icon-class',
'hover-class',
],
mixins: [link],
props: {
title: null,
value: null,
icon: String,
size: String,
label: String,
center: Boolean,
isLink: Boolean,
required: Boolean,
clickable: Boolean,
titleWidth: String,
customStyle: String,
arrowDirection: String,
useLabelSlot: Boolean,
border: {
type: Boolean,
value: true,
},
titleStyle: String,
},
methods: {
onClick(event) {
this.$emit('click', event.detail);
this.jumpLink();
},
},
});
{
"component": true,
"usingComponents": {
"van-icon": "../icon/index"
}
}
<wxs src="../wxs/utils.wxs" module="utils" />
<wxs src="./index.wxs" module="computed" />
<view
class="custom-class {{ utils.bem('cell', [size, { center, required, borderless: !border, clickable: isLink || clickable }]) }}"
hover-class="van-cell--hover hover-class"
hover-stay-time="70"
style="{{ customStyle }}"
bind:tap="onClick"
>
<van-icon
wx:if="{{ icon }}"
name="{{ icon }}"
class="van-cell__left-icon-wrap"
custom-class="van-cell__left-icon"
/>
<slot wx:else name="icon" />
<view
style="{{ computed.titleStyle({ titleWidth, titleStyle }) }}"
class="van-cell__title title-class"
>
<block wx:if="{{ title }}">{{ title }}</block>
<slot wx:else name="title" />
<view wx:if="{{ label || useLabelSlot }}" class="van-cell__label label-class">
<slot wx:if="{{ useLabelSlot }}" name="label" />
<block wx:elif="{{ label }}">{{ label }}</block>
</view>
</view>
<view class="van-cell__value value-class">
<block wx:if="{{ value || value === 0 }}">{{ value }}</block>
<slot wx:else />
</view>
<van-icon
wx:if="{{ isLink }}"
name="{{ arrowDirection ? 'arrow' + '-' + arrowDirection : 'arrow' }}"
class="van-cell__right-icon-wrap right-icon-class"
custom-class="van-cell__right-icon"
/>
<slot wx:else name="right-icon" />
<slot name="extra" />
</view>
/* eslint-disable */
var style = require('../wxs/style.wxs');
var addUnit = require('../wxs/add-unit.wxs');
function titleStyle(data) {
return style([
{
'max-width': addUnit(data.titleWidth),
'min-width': addUnit(data.titleWidth),
},
data.titleStyle,
]);
}
module.exports = {
titleStyle: titleStyle,
};
@import '../common/index.wxss';.van-cell{background-color:var(--cell-background-color,#fff);box-sizing:border-box;color:var(--cell-text-color,#323233);display:flex;font-size:var(--cell-font-size,14px);line-height:var(--cell-line-height,24px);padding:var(--cell-vertical-padding,10px) var(--cell-horizontal-padding,16px);position:relative;width:100%}.van-cell:after{border-bottom:1px solid #ebedf0;bottom:0;box-sizing:border-box;content:" ";left:16px;pointer-events:none;position:absolute;right:16px;transform:scaleY(.5);transform-origin:center}.van-cell--borderless:after{display:none}.van-cell-group{background-color:var(--cell-background-color,#fff)}.van-cell__label{color:var(--cell-label-color,#969799);font-size:var(--cell-label-font-size,12px);line-height:var(--cell-label-line-height,18px);margin-top:var(--cell-label-margin-top,3px)}.van-cell__value{color:var(--cell-value-color,#969799);overflow:hidden;text-align:right;vertical-align:middle}.van-cell__title,.van-cell__value{flex:1}.van-cell__title:empty,.van-cell__value:empty{display:none}.van-cell__left-icon-wrap,.van-cell__right-icon-wrap{align-items:center;display:flex;font-size:var(--cell-icon-size,16px);height:var(--cell-line-height,24px)}.van-cell__left-icon-wrap{margin-right:var(--padding-base,4px)}.van-cell__right-icon-wrap{color:var(--cell-right-icon-color,#969799);margin-left:var(--padding-base,4px)}.van-cell__left-icon{vertical-align:middle}.van-cell__left-icon,.van-cell__right-icon{line-height:var(--cell-line-height,24px)}.van-cell--clickable.van-cell--hover{background-color:var(--cell-active-color,#f2f3f5)}.van-cell--required{overflow:visible}.van-cell--required:before{color:var(--cell-required-color,#ee0a24);content:"*";font-size:var(--cell-font-size,14px);left:var(--padding-xs,8px);position:absolute}.van-cell--center{align-items:center}.van-cell--large{padding-bottom:var(--cell-large-vertical-padding,12px);padding-top:var(--cell-large-vertical-padding,12px)}.van-cell--large .van-cell__title{font-size:var(--cell-large-title-font-size,16px)}.van-cell--large .van-cell__value{font-size:var(--cell-large-value-font-size,16px)}.van-cell--large .van-cell__label{font-size:var(--cell-large-label-font-size,14px)}
\ No newline at end of file
import { useChildren } from '../common/relation';
import { VantComponent } from '../common/component';
VantComponent({
field: true,
relation: useChildren('checkbox', function (target) {
this.updateChild(target);
}),
props: {
max: Number,
value: {
type: Array,
observer: 'updateChildren',
},
disabled: {
type: Boolean,
observer: 'updateChildren',
},
direction: {
type: String,
value: 'vertical',
},
},
methods: {
updateChildren() {
this.children.forEach((child) => this.updateChild(child));
},
updateChild(child) {
const { value, disabled, direction } = this.data;
child.setData({
value: value.indexOf(child.data.name) !== -1,
parentDisabled: disabled,
direction,
});
},
},
});
<wxs src="../wxs/utils.wxs" module="utils" />
<view class="{{ utils.bem('checkbox-group', [{ horizontal: direction === 'horizontal' }]) }}">
<slot />
</view>
@import '../common/index.wxss';.van-checkbox-group--horizontal{display:flex;flex-wrap:wrap}
\ No newline at end of file
import { useParent } from '../common/relation';
import { VantComponent } from '../common/component';
function emit(target, value) {
target.$emit('input', value);
target.$emit('change', value);
}
VantComponent({
field: true,
relation: useParent('checkbox-group'),
classes: ['icon-class', 'label-class'],
props: {
value: Boolean,
disabled: Boolean,
useIconSlot: Boolean,
checkedColor: String,
labelPosition: {
type: String,
value: 'right',
},
labelDisabled: Boolean,
shape: {
type: String,
value: 'round',
},
iconSize: {
type: null,
value: 20,
},
},
data: {
parentDisabled: false,
direction: 'vertical',
},
methods: {
emitChange(value) {
if (this.parent) {
this.setParentValue(this.parent, value);
}
else {
emit(this, value);
}
},
toggle() {
const { parentDisabled, disabled, value } = this.data;
if (!disabled && !parentDisabled) {
this.emitChange(!value);
}
},
onClickLabel() {
const { labelDisabled, parentDisabled, disabled, value } = this.data;
if (!disabled && !labelDisabled && !parentDisabled) {
this.emitChange(!value);
}
},
setParentValue(parent, value) {
const parentValue = parent.data.value.slice();
const { name } = this.data;
const { max } = parent.data;
if (value) {
if (max && parentValue.length >= max) {
return;
}
if (parentValue.indexOf(name) === -1) {
parentValue.push(name);
emit(parent, parentValue);
}
}
else {
const index = parentValue.indexOf(name);
if (index !== -1) {
parentValue.splice(index, 1);
emit(parent, parentValue);
}
}
},
},
});
{
"component": true,
"usingComponents": {
"van-icon": "../icon/index"
}
}
<wxs src="../wxs/utils.wxs" module="utils" />
<wxs src="./index.wxs" module="computed" />
<view class="{{ utils.bem('checkbox', [{ horizontal: direction === 'horizontal' }]) }} custom-class">
<view
wx:if="{{ labelPosition === 'left' }}"
class="label-class {{ utils.bem('checkbox__label', [labelPosition, { disabled: disabled || parentDisabled }]) }}"
bindtap="onClickLabel"
>
<slot />
</view>
<view class="van-checkbox__icon-wrap" bindtap="toggle">
<slot wx:if="{{ useIconSlot }}" name="icon" />
<van-icon
wx:else
name="success"
size="0.8em"
class="{{ utils.bem('checkbox__icon', [shape, { disabled: disabled || parentDisabled, checked: value }]) }}"
style="{{ computed.iconStyle(checkedColor, value, disabled, parentDisabled, iconSize) }}"
custom-class="icon-class"
custom-style="line-height: 1.25em;"
/>
</view>
<view
wx:if="{{ labelPosition === 'right' }}"
class="label-class {{ utils.bem('checkbox__label', [labelPosition, { disabled: disabled || parentDisabled }]) }}"
bindtap="onClickLabel"
>
<slot />
</view>
</view>
/* eslint-disable */
var style = require('../wxs/style.wxs');
var addUnit = require('../wxs/add-unit.wxs');
function iconStyle(checkedColor, value, disabled, parentDisabled, iconSize) {
var styles = {
'font-size': addUnit(iconSize),
};
if (checkedColor && value && !disabled && !parentDisabled) {
styles['border-color'] = checkedColor;
styles['background-color'] = checkedColor;
}
return style(styles);
}
module.exports = {
iconStyle: iconStyle,
};
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment