feat: date-only operators (#50)

* feat: date-only operators

* feat: add opposite operators

* fix: use moment to parse date string

* fix: remove additional moment dependency.

* modify date filter operators

Co-authored-by: chenos <chenlinxh@gmail.com>
This commit is contained in:
Junyi 2020-12-30 14:05:28 +08:00 committed by GitHub
parent d74eda3f23
commit 435cd9a7c9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 245 additions and 16 deletions

View File

@ -171,12 +171,12 @@ const OP_MAP = {
{label: '为空', value: '$null'}, {label: '为空', value: '$null'},
], ],
datetime: [ datetime: [
{label: '等于', value: 'eq', selected: true}, {label: '等于', value: '$dateOn', selected: true},
{label: '不等于', value: 'neq'}, {label: '不等于', value: '$dateNotOn'},
{label: '大于', value: 'gt'}, {label: '早于', value: '$dateBefore'},
{label: '大于等于', value: 'gte'}, {label: '晚于', value: '$dateAfter'},
{label: '小于', value: 'lt'}, {label: '不早于', value: '$dateNotBefore'},
{label: '小于等于', value: 'lte'}, {label: '不晚于', value: '$dateNotAfter'},
// {label: '介于', value: 'between'}, // {label: '介于', value: 'between'},
{label: '非空', value: '$notNull'}, {label: '非空', value: '$notNull'},
{label: '为空', value: '$null'}, {label: '为空', value: '$null'},
@ -257,15 +257,20 @@ const controls = {
function DateControl(props: any) { function DateControl(props: any) {
const { field, value, onChange, ...restProps } = props; const { field, value, onChange, ...restProps } = props;
let format = field.dateFormat; let format = field.dateFormat;
if (field.showTime) { // if (field.showTime) {
format += ` ${field.timeFormat}`; // format += ` ${field.timeFormat}`;
} // }
const m = moment(value, format); const m = moment(value, format);
return ( return (
<DatePicker format={format} showTime={field.showTime} value={m.isValid() ? m : null} onChange={(value) => { <DatePicker format={format} value={m.isValid() ? m : null} onChange={(value) => {
onChange(value ? value.format(field.showTime ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD') : null) onChange(value ? value.format('YYYY-MM-DD') : null)
}}/> }}/>
); );
// return (
// <DatePicker format={format} showTime={field.showTime} value={m.isValid() ? m : null} onChange={(value) => {
// onChange(value ? value.format(field.showTime ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD') : null)
// }}/>
// );
} }
function TimeControl(props: any) { function TimeControl(props: any) {

View File

@ -4,8 +4,7 @@
"description": "", "description": "",
"main": "./lib/index.js", "main": "./lib/index.js",
"types": "./lib/index.d.ts", "types": "./lib/index.d.ts",
"scripts": { "scripts": {},
},
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"bcrypt": "^5.0.0", "bcrypt": "^5.0.0",

View File

@ -24,7 +24,7 @@ const config = {
}, },
}, },
}, },
logging: process.env.DB_LOG_SQL === 'on' logging: process.env.DB_LOG_SQL === 'on' ? console.log : false
}; };
export function getDatabase() { export function getDatabase() {

View File

@ -0,0 +1,197 @@
import { Op } from 'sequelize';
import Database from '../../database';
import { toWhere } from '../../utils';
import { getDatabase } from '..';
describe('utils.toWhere', () => {
let db: Database;
beforeEach(async () => {
db = getDatabase();
db.table({
name: 'users',
fields: [
{
type: 'hasMany',
name: 'posts',
}
],
});
db.table({
name: 'posts',
fields: [
{
type: 'string',
name: 'title'
},
{
type: 'belongsTo',
name: 'user',
}
]
});
await db.sync({ force: true });
const Post = db.getModel('posts');
await Post.bulkCreate(Array(5).fill(null).map((_, i) => ({
title: `title${i}`,
created_at: new Date(2020, 11, 29 + i)
})));
});
afterEach(async () => await db.close());
describe('date', () => {
it('$dateOn', async () => {
const Post = db.getModel('posts');
const posts = await Post.findAll({
where: toWhere({
'created_at.$dateOn': '2020-12-31'
})
});
expect(posts[0].created_at).toEqual(new Date(2020, 11,31));
});
it('$dateNotOn', async () => {
const Post = db.getModel('posts');
const posts = await Post.findAll({
where: toWhere({
'created_at.$dateNotOn': '2020-12-31'
})
});
expect(posts.length).toBe(4);
});
it('Op.$dateBefore', async () => {
const Post = db.getModel('posts');
const posts = await Post.findAll({
where: toWhere({
'created_at.$dateBefore': '2020-12-29'
})
});
expect(posts.length).toBe(0);
});
it('Op.$dateBefore', async () => {
const Post = db.getModel('posts');
const posts = await Post.findAll({
where: toWhere({
'created_at.$dateBefore': '2021-01-02'
})
});
expect(posts.length).toBe(4);
});
it('Op.$dateBefore', async () => {
const Post = db.getModel('posts');
const posts = await Post.findAll({
where: toWhere({
'created_at.$dateBefore': '2021-01-03'
})
});
expect(posts.length).toBe(5);
});
it('Op.$dateAfter', async () => {
const Post = db.getModel('posts');
const posts = await Post.findAll({
where: toWhere({
'created_at.$dateAfter': '2021-01-03'
})
});
expect(posts.length).toBe(0);
});
it('Op.$dateAfter', async () => {
const Post = db.getModel('posts');
const posts = await Post.findAll({
where: toWhere({
'created_at.$dateAfter': '2021-01-03'
})
});
expect(posts.length).toBe(0);
});
it('Op.$dateAfter', async () => {
const Post = db.getModel('posts');
const posts = await Post.findAll({
where: toWhere({
'created_at.$dateAfter': '2020-12-29'
})
});
expect(posts.length).toBe(4);
});
it('Op.$dateNotBefore', async () => {
const Post = db.getModel('posts');
const posts = await Post.findAll({
where: toWhere({
'created_at.$dateNotBefore': '2020-12-29'
})
});
expect(posts.length).toBe(5);
});
it('Op.$dateNotAfter', async () => {
const Post = db.getModel('posts');
const posts = await Post.findAll({
where: toWhere({
'created_at.$dateNotAfter': '2020-12-29'
})
});
expect(posts.length).toBe(1);
});
it('Op.$dateBetween', async () => {
const Post = db.getModel('posts');
const posts = await Post.findAll({
where: toWhere({
'created_at.$dateBetween': ['2020-12-29', '2021-01-01']
})
});
expect(posts.length).toBe(4);
});
it('Op.$dateBetween', async () => {
const Post = db.getModel('posts');
const posts = await Post.findAll({
where: toWhere({
'created_at.$dateBetween': ['2020-12-28', '2020-12-29']
})
});
expect(posts.length).toBe(1);
});
it('Op.$dateBetween', async () => {
const Post = db.getModel('posts');
const posts = await Post.findAll({
where: toWhere({
'created_at.$dateBetween': ['2021-01-03', '2021-01-04']
})
});
expect(posts.length).toBe(0);
});
it('Op.$dateNotBetween', async () => {
const Post = db.getModel('posts');
const posts = await Post.findAll({
where: toWhere({
'created_at.$dateNotBetween': ['2020-12-28', '2020-12-29']
})
});
expect(posts.length).toBe(4);
});
it('Op.$dateNotBetween', async () => {
const Post = db.getModel('posts');
const posts = await Post.findAll({
where: toWhere({
'created_at.$dateNotBetween': ['2020-12-28', '2021-01-04']
})
});
expect(posts.length).toBe(0);
});
});
});

View File

@ -1,4 +1,5 @@
import { Op, Utils, Sequelize } from 'sequelize'; import { Op, Utils, Sequelize } from 'sequelize';
import moment, { MomentInput } from 'moment';
function toArray(value: any): any[] { function toArray(value: any): any[] {
if (value == null) { if (value == null) {
@ -7,6 +8,14 @@ function toArray(value: any): any[] {
return Array.isArray(value) ? value : [value]; return Array.isArray(value) ? value : [value];
} }
function stringToDate(value: string): Date {
return moment(value).toDate();
}
function getNextDay(value: MomentInput): Date {
return moment(value).add(1, 'd').toDate();
}
const op = new Map<string, typeof Op | Function>(); const op = new Map<string, typeof Op | Function>();
// Sequelize 内置 // Sequelize 内置
@ -56,6 +65,25 @@ op.set('$endsWith', (value: string) => ({ [Op.iLike]: `%${value}` }));
// 不以之结束 // 不以之结束
op.set('$notEndsWith', (value: string) => ({ [Op.notILike]: `%${value}` })); op.set('$notEndsWith', (value: string) => ({ [Op.notILike]: `%${value}` }));
// 仅日期
// 在某日
op.set('$dateOn', (value: string) => ({ [Op.and]: [{ [Op.gte]: stringToDate(value) }, { [Op.lt]: getNextDay(value) }] }));
// 不在某日
op.set('$dateNotOn', (value: string) => ({ [Op.or]: [{ [Op.lt]: stringToDate(value) }, { [Op.gte]: getNextDay(value) }] }));
// 某日前
op.set('$dateBefore', (value: string) => ({ [Op.lt]: stringToDate(value) }));
// 某日后
op.set('$dateAfter', (value: string) => ({ [Op.gte]: getNextDay(value) }));
// 不早于(含当天)
op.set('$dateNotBefore', (value: string) => ({ [Op.gte]: stringToDate(value) }));
// 不晚于(含当天)
op.set('$dateNotAfter', (value: string) => ({ [Op.lt]: getNextDay(value) }));
// 在期间
op.set('$dateBetween', ([from, to]: string[]) => ({ [Op.and]: [{ [Op.gte]: stringToDate(from) }, { [Op.lt]: getNextDay(to) }] }));
// 不在期间
op.set('$dateNotBetween', ([from, to]: string[]) => ({ [Op.or]: [{ [Op.lt]: stringToDate(from) }, { [Op.gte]: getNextDay(to) }] }));
// 多选JSON类型 // 多选JSON类型
// 包含组中任意值(命名来源:`Array.prototype.some` // 包含组中任意值(命名来源:`Array.prototype.some`

View File

@ -351,7 +351,7 @@ export const createdAt = {
type: 'date', type: 'date',
// name: 'created_at', // name: 'created_at',
field: 'created_at', field: 'created_at',
showTime: true, showTime: false,
dateFormat: 'YYYY-MM-DD', dateFormat: 'YYYY-MM-DD',
timeFormat: 'HH:mm:ss', timeFormat: 'HH:mm:ss',
required: true, required: true,
@ -370,7 +370,7 @@ export const updatedAt = {
type: 'date', type: 'date',
// name: 'updated_at', // name: 'updated_at',
field: 'updated_at', field: 'updated_at',
showTime: true, showTime: false,
dateFormat: 'YYYY-MM-DD', dateFormat: 'YYYY-MM-DD',
timeFormat: 'HH:mm:ss', timeFormat: 'HH:mm:ss',
required: true, required: true,