Compare commits
3 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
d2d8a6d258 | 4 months ago |
|
|
4532f2c4cb | 4 months ago |
|
|
5deb9eee6d | 4 months ago |
@ -1,3 +0,0 @@
|
||||
.idea
|
||||
__pycache__/
|
||||
*.log
|
||||
@ -1,7 +0,0 @@
|
||||
FROM python:3.12.4-slim
|
||||
WORKDIR /app
|
||||
COPY requirements.txt requirements.txt
|
||||
RUN pip config set global.index-url http://mirrors.aliyun.com/pypi/simple && pip config set install.trusted-host mirrors.aliyun.com
|
||||
RUN pip install -r requirements.txt
|
||||
COPY . ./
|
||||
CMD ["gunicorn", "-w", "4", "--bind", "0.0.0.0:3002", "myapp:app" ]
|
||||
@ -1,10 +0,0 @@
|
||||
# 从 app 模块导入 db 对象,不过该导入未被使用,后续可考虑移除
|
||||
from app import db
|
||||
# 从当前包的 order_lib 模块导入 Order 类
|
||||
from .order_lib import Order
|
||||
# 从当前包的 passenger_lib 模块导入 Passenger 类
|
||||
from .passenger_lib import Passenger
|
||||
# 从当前包的 station_lib 模块导入 Station 类
|
||||
from .station_lib import Station
|
||||
# 从当前包的 train_lib 模块导入 Train 类
|
||||
from .train_lib import Train
|
||||
@ -1,40 +0,0 @@
|
||||
version: '3.8'
|
||||
services:
|
||||
python:
|
||||
build:
|
||||
context: ./
|
||||
dockerfile: ./Dockerfile
|
||||
restart: always
|
||||
environment:
|
||||
- DATABASE_URL=postgresql://postgres:postgres@db:5432/mini12306_python
|
||||
- REDIS_URL=redis
|
||||
- SERVER_LIB_URL=http://py12306.learnerhub.net
|
||||
ports:
|
||||
- "3002:3002"
|
||||
depends_on:
|
||||
- db
|
||||
- redis
|
||||
volumes:
|
||||
- /var/log/mini12306_python:/app/logs
|
||||
|
||||
redis:
|
||||
image: redis:alpine
|
||||
container_name: 12306_redis
|
||||
volumes:
|
||||
- redis_data:/data
|
||||
|
||||
db:
|
||||
image: postgres:15
|
||||
container_name: 12306_postgres
|
||||
restart: always
|
||||
environment:
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
POSTGRES_DB: mini12306_python
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
redis_data:
|
||||
@ -1 +0,0 @@
|
||||
init folder
|
||||
@ -1 +0,0 @@
|
||||
Single-database configuration for Flask.
|
||||
@ -1,50 +0,0 @@
|
||||
# A generic, single database configuration.
|
||||
|
||||
[alembic]
|
||||
# template used to generate migration files
|
||||
# file_template = %%(rev)s_%%(slug)s
|
||||
|
||||
# set to 'true' to run the environment during
|
||||
# the 'revision' command, regardless of autogenerate
|
||||
# revision_environment = false
|
||||
|
||||
|
||||
# Logging configuration
|
||||
[loggers]
|
||||
keys = root,sqlalchemy,alembic,flask_migrate
|
||||
|
||||
[handlers]
|
||||
keys = console
|
||||
|
||||
[formatters]
|
||||
keys = generic
|
||||
|
||||
[logger_root]
|
||||
level = WARN
|
||||
handlers = console
|
||||
qualname =
|
||||
|
||||
[logger_sqlalchemy]
|
||||
level = WARN
|
||||
handlers =
|
||||
qualname = sqlalchemy.engine
|
||||
|
||||
[logger_alembic]
|
||||
level = INFO
|
||||
handlers =
|
||||
qualname = alembic
|
||||
|
||||
[logger_flask_migrate]
|
||||
level = INFO
|
||||
handlers =
|
||||
qualname = flask_migrate
|
||||
|
||||
[handler_console]
|
||||
class = StreamHandler
|
||||
args = (sys.stderr,)
|
||||
level = NOTSET
|
||||
formatter = generic
|
||||
|
||||
[formatter_generic]
|
||||
format = %(levelname)-5.5s [%(name)s] %(message)s
|
||||
datefmt = %H:%M:%S
|
||||
@ -1,24 +0,0 @@
|
||||
"""${message}
|
||||
|
||||
Revision ID: ${up_revision}
|
||||
Revises: ${down_revision | comma,n}
|
||||
Create Date: ${create_date}
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
${imports if imports else ""}
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = ${repr(up_revision)}
|
||||
down_revision = ${repr(down_revision)}
|
||||
branch_labels = ${repr(branch_labels)}
|
||||
depends_on = ${repr(depends_on)}
|
||||
|
||||
|
||||
def upgrade():
|
||||
${upgrades if upgrades else "pass"}
|
||||
|
||||
|
||||
def downgrade():
|
||||
${downgrades if downgrades else "pass"}
|
||||
@ -1,178 +0,0 @@
|
||||
"""init databases
|
||||
|
||||
Revision ID: 1b3bc6809b30
|
||||
Revises:
|
||||
Create Date: 2024-08-15 15:09:26.124279
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '1b3bc6809b30'
|
||||
down_revision = None
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table('log',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('log_user_agent', sa.String(length=120), nullable=True),
|
||||
sa.Column('log_method', sa.String(length=120), nullable=True),
|
||||
sa.Column('log_quest_path', sa.String(length=120), nullable=True),
|
||||
sa.Column('log_ip', sa.String(length=120), nullable=True),
|
||||
sa.Column('log_params', sa.String(length=120), nullable=True),
|
||||
sa.Column('log_controller', sa.String(length=120), nullable=True),
|
||||
sa.Column('log_controller_action', sa.String(length=120), nullable=True),
|
||||
sa.Column('log_controller_id', sa.String(length=120), nullable=True),
|
||||
sa.Column('account_id', sa.String(length=120), nullable=True),
|
||||
sa.Column('event_name', sa.String(length=120), nullable=True),
|
||||
sa.Column('event_content', sa.Text(), nullable=True),
|
||||
sa.Column('operator', sa.String(length=120), nullable=True),
|
||||
sa.Column('created_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('updated_at', sa.DateTime(), nullable=True),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_table('passenger',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('name', sa.String(length=120), nullable=True),
|
||||
sa.Column('account', sa.String(length=120), nullable=False),
|
||||
sa.Column('password_digest', sa.String(length=2000), nullable=False),
|
||||
sa.Column('id_card_no', sa.String(length=120), nullable=True),
|
||||
sa.Column('mobile_no', sa.String(length=120), nullable=True),
|
||||
sa.Column('bank_card_no', sa.String(length=120), nullable=True),
|
||||
sa.Column('state', sa.Integer(), nullable=True),
|
||||
sa.Column('member_type', sa.Integer(), nullable=True),
|
||||
sa.Column('last_login_time', sa.DateTime(), nullable=True),
|
||||
sa.Column('created_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('updated_at', sa.DateTime(), nullable=True),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
with op.batch_alter_table('passenger', schema=None) as batch_op:
|
||||
batch_op.create_index(batch_op.f('ix_passenger_account'), ['account'], unique=True)
|
||||
|
||||
op.create_table('station',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('name', sa.String(length=120), nullable=False),
|
||||
sa.Column('pinyin', sa.String(length=120), nullable=True),
|
||||
sa.Column('province', sa.String(length=120), nullable=True),
|
||||
sa.Column('city', sa.String(length=120), nullable=True),
|
||||
sa.Column('district', sa.String(length=120), nullable=True),
|
||||
sa.Column('created_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('updated_at', sa.DateTime(), nullable=True),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
with op.batch_alter_table('station', schema=None) as batch_op:
|
||||
batch_op.create_index(batch_op.f('ix_station_name'), ['name'], unique=True)
|
||||
|
||||
op.create_table('train',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('train_no', sa.String(length=120), nullable=False),
|
||||
sa.Column('departure_station', sa.String(length=120), nullable=True),
|
||||
sa.Column('arrival_station', sa.String(length=120), nullable=True),
|
||||
sa.Column('departure_time', sa.DateTime(), nullable=True),
|
||||
sa.Column('expiration_time', sa.DateTime(), nullable=True),
|
||||
sa.Column('effective_time', sa.DateTime(), nullable=True),
|
||||
sa.Column('arrival_time', sa.DateTime(), nullable=True),
|
||||
sa.Column('created_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('updated_at', sa.DateTime(), nullable=True),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
with op.batch_alter_table('train', schema=None) as batch_op:
|
||||
batch_op.create_index(batch_op.f('ix_train_train_no'), ['train_no'], unique=True)
|
||||
|
||||
op.create_table('order',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('order_no', sa.String(length=120), nullable=False),
|
||||
sa.Column('price', sa.Numeric(precision=8, scale=2), nullable=True),
|
||||
sa.Column('payment_time', sa.DateTime(), nullable=True),
|
||||
sa.Column('state', sa.Integer(), nullable=True),
|
||||
sa.Column('passenger_id', sa.Integer(), nullable=False),
|
||||
sa.Column('created_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('updated_at', sa.DateTime(), nullable=True),
|
||||
sa.ForeignKeyConstraint(['passenger_id'], ['passenger.id'], ),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('order_no')
|
||||
)
|
||||
with op.batch_alter_table('order', schema=None) as batch_op:
|
||||
batch_op.create_index(batch_op.f('ix_order_passenger_id'), ['passenger_id'], unique=False)
|
||||
|
||||
op.create_table('train_station',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('train_no', sa.String(length=120), nullable=False),
|
||||
sa.Column('station_name', sa.String(length=120), nullable=False),
|
||||
sa.Column('price', sa.Numeric(precision=8, scale=2), nullable=True),
|
||||
sa.Column('arrival_time', sa.DateTime(), nullable=True),
|
||||
sa.Column('departure_time', sa.DateTime(), nullable=True),
|
||||
sa.Column('index', sa.Integer(), nullable=True),
|
||||
sa.Column('created_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('updated_at', sa.DateTime(), nullable=True),
|
||||
sa.ForeignKeyConstraint(['station_name'], ['station.name'], ),
|
||||
sa.ForeignKeyConstraint(['train_no'], ['train.train_no'], ),
|
||||
sa.PrimaryKeyConstraint('id', 'train_no', 'station_name')
|
||||
)
|
||||
with op.batch_alter_table('train_station', schema=None) as batch_op:
|
||||
batch_op.create_index(batch_op.f('ix_train_station_train_no'), ['train_no'], unique=False)
|
||||
|
||||
op.create_table('ticket',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('seat_no', sa.String(length=120), nullable=True),
|
||||
sa.Column('seat_class', sa.String(length=120), nullable=True),
|
||||
sa.Column('price', sa.Numeric(precision=8, scale=2), nullable=True),
|
||||
sa.Column('state', sa.Integer(), nullable=True),
|
||||
sa.Column('train_no', sa.String(length=120), nullable=False),
|
||||
sa.Column('passenger_id', sa.Integer(), nullable=False),
|
||||
sa.Column('order_id', sa.Integer(), nullable=False),
|
||||
sa.Column('from_station', sa.String(length=120), nullable=True),
|
||||
sa.Column('to_station', sa.String(length=120), nullable=True),
|
||||
sa.Column('date', sa.String(length=120), nullable=True),
|
||||
sa.Column('departure_time', sa.DateTime(), nullable=True),
|
||||
sa.Column('arrival_time', sa.DateTime(), nullable=True),
|
||||
sa.Column('created_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('updated_at', sa.DateTime(), nullable=True),
|
||||
sa.ForeignKeyConstraint(['order_id'], ['order.id'], ),
|
||||
sa.ForeignKeyConstraint(['passenger_id'], ['passenger.id'], ),
|
||||
sa.ForeignKeyConstraint(['train_no'], ['train.train_no'], ),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
with op.batch_alter_table('ticket', schema=None) as batch_op:
|
||||
batch_op.create_index(batch_op.f('ix_ticket_order_id'), ['order_id'], unique=False)
|
||||
batch_op.create_index(batch_op.f('ix_ticket_passenger_id'), ['passenger_id'], unique=False)
|
||||
batch_op.create_index(batch_op.f('ix_ticket_train_no'), ['train_no'], unique=False)
|
||||
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
with op.batch_alter_table('ticket', schema=None) as batch_op:
|
||||
batch_op.drop_index(batch_op.f('ix_ticket_train_no'))
|
||||
batch_op.drop_index(batch_op.f('ix_ticket_passenger_id'))
|
||||
batch_op.drop_index(batch_op.f('ix_ticket_order_id'))
|
||||
|
||||
op.drop_table('ticket')
|
||||
with op.batch_alter_table('train_station', schema=None) as batch_op:
|
||||
batch_op.drop_index(batch_op.f('ix_train_station_train_no'))
|
||||
|
||||
op.drop_table('train_station')
|
||||
with op.batch_alter_table('order', schema=None) as batch_op:
|
||||
batch_op.drop_index(batch_op.f('ix_order_passenger_id'))
|
||||
|
||||
op.drop_table('order')
|
||||
with op.batch_alter_table('train', schema=None) as batch_op:
|
||||
batch_op.drop_index(batch_op.f('ix_train_train_no'))
|
||||
|
||||
op.drop_table('train')
|
||||
with op.batch_alter_table('station', schema=None) as batch_op:
|
||||
batch_op.drop_index(batch_op.f('ix_station_name'))
|
||||
|
||||
op.drop_table('station')
|
||||
with op.batch_alter_table('passenger', schema=None) as batch_op:
|
||||
batch_op.drop_index(batch_op.f('ix_passenger_account'))
|
||||
|
||||
op.drop_table('passenger')
|
||||
op.drop_table('log')
|
||||
# ### end Alembic commands ###
|
||||
@ -1,32 +0,0 @@
|
||||
"""remove TrainStation id
|
||||
|
||||
Revision ID: 7866a6e8a75b
|
||||
Revises: 1b3bc6809b30
|
||||
Create Date: 2024-08-15 15:29:40.410360
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '7866a6e8a75b'
|
||||
down_revision = '1b3bc6809b30'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
with op.batch_alter_table('train_station', schema=None) as batch_op:
|
||||
batch_op.drop_column('id')
|
||||
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
with op.batch_alter_table('train_station', schema=None) as batch_op:
|
||||
batch_op.add_column(sa.Column('id', sa.INTEGER(), autoincrement=False, nullable=False))
|
||||
|
||||
# ### end Alembic commands ###
|
||||
@ -0,0 +1,32 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
.DS_Store
|
||||
dist
|
||||
dist-ssr
|
||||
coverage
|
||||
*.local
|
||||
|
||||
/cypress/videos/
|
||||
/cypress/screenshots/
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
*.tsbuildinfo
|
||||
|
||||
/unpackage/*
|
||||
@ -0,0 +1,198 @@
|
||||
<script>
|
||||
import apiAuth from "@/api/auth.js";
|
||||
import apiStations from "@/api/stations";
|
||||
|
||||
export default {
|
||||
// 生命周期
|
||||
onLaunch: function () {
|
||||
console.log("App Launch");
|
||||
|
||||
// 根据 token 获取用户信息
|
||||
const authToken = uni.getStorageSync("auth-token");
|
||||
if (authToken) {
|
||||
apiAuth
|
||||
.detail({ authToken })
|
||||
.then((res) => {
|
||||
this.$store.dispatch("updateAuth", { ...res.data, token: authToken });
|
||||
})
|
||||
.catch(() => {
|
||||
uni.removeStorageSync("auth-token");
|
||||
});
|
||||
}
|
||||
|
||||
// 获取所有站点
|
||||
apiStations
|
||||
.get()
|
||||
.then((res) => this.$store.dispatch("updateStations", res.data))
|
||||
.catch(() => {});
|
||||
},
|
||||
onShow: function () {
|
||||
console.log("App Show");
|
||||
},
|
||||
onHide: function () {
|
||||
console.log("App Hide");
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import "uview-ui/index.scss";
|
||||
@import "uview-ui/libs/css/flex.scss";
|
||||
|
||||
.u-flex-shrink-0 {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* 间距 $spacers 在 uni.scss 中 */
|
||||
@each $key, $val in $spacers {
|
||||
.mt-#{$key},
|
||||
.my-#{$key},
|
||||
.m-#{$key} {
|
||||
margin-top: $val;
|
||||
}
|
||||
.mb-#{$key},
|
||||
.my-#{$key},
|
||||
.m-#{$key} {
|
||||
margin-bottom: $val;
|
||||
}
|
||||
.ms-#{$key},
|
||||
.mx-#{$key},
|
||||
.m-#{$key} {
|
||||
margin-left: $val;
|
||||
}
|
||||
.me-#{$key},
|
||||
.mx-#{$key},
|
||||
.m-#{$key} {
|
||||
margin-right: $val;
|
||||
}
|
||||
.pt-#{$key},
|
||||
.py-#{$key},
|
||||
.p-#{$key} {
|
||||
padding-top: $val;
|
||||
}
|
||||
.pb-#{$key},
|
||||
.py-#{$key},
|
||||
.p-#{$key} {
|
||||
padding-bottom: $val;
|
||||
}
|
||||
.ps-#{$key},
|
||||
.px-#{$key},
|
||||
.p-#{$key} {
|
||||
padding-left: $val;
|
||||
}
|
||||
.pe-#{$key},
|
||||
.px-#{$key},
|
||||
.p-#{$key} {
|
||||
padding-right: $val;
|
||||
}
|
||||
|
||||
.rounded-#{$key} {
|
||||
border-radius: $val;
|
||||
}
|
||||
}
|
||||
:root {
|
||||
@each $key, $val in $spacers {
|
||||
--space-#{$key}: #{$val};
|
||||
--rounded-#{$key}: #{$val};
|
||||
}
|
||||
}
|
||||
|
||||
/* 颜色 $colors 在 uni.scss 中 */
|
||||
@each $key, $val in $colors {
|
||||
.text-#{$key} {
|
||||
color: $val;
|
||||
}
|
||||
}
|
||||
:root {
|
||||
@each $key, $val in $colors {
|
||||
--color-#{$key}: #{$val};
|
||||
}
|
||||
}
|
||||
|
||||
/* 每个页面公共css */
|
||||
page {
|
||||
background-color: $u-bg-color; // uview-ui/theme.scss
|
||||
}
|
||||
|
||||
.p-page {
|
||||
@extend .p-base;
|
||||
}
|
||||
|
||||
.page-box-x-overflow {
|
||||
margin-left: calc(var(--space-base) * -1);
|
||||
margin-right: calc(var(--space-base) * -1);
|
||||
}
|
||||
.page-box-t-overflow {
|
||||
margin-top: calc(var(--space-base) * -1);
|
||||
}
|
||||
|
||||
.page-box {
|
||||
@extend .bg-white, .rounded-sm;
|
||||
}
|
||||
.page-box + .page-box {
|
||||
@extend .mt-base;
|
||||
}
|
||||
|
||||
.relative {
|
||||
position: relative;
|
||||
}
|
||||
.absolute {
|
||||
position: absolute;
|
||||
}
|
||||
.overflow-hidden {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.bg-white {
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.fw-bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
.text-end {
|
||||
text-align: end;
|
||||
}
|
||||
|
||||
.w-100 {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.TrainItem {
|
||||
.OrderInfo {
|
||||
font-size: 14px;
|
||||
color: var(--color-info);
|
||||
}
|
||||
|
||||
.Time {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
}
|
||||
.Station {
|
||||
font-size: 14px;
|
||||
color: var(--color-main);
|
||||
}
|
||||
|
||||
.OptionItem {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding-top: var(--space-base);
|
||||
padding-bottom: var(--space-base);
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
&.lg {
|
||||
.Time {
|
||||
font-size: 22px;
|
||||
}
|
||||
.Station {
|
||||
font-size: 15px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
After Width: | Height: | Size: 3.9 KiB |
@ -0,0 +1,12 @@
|
||||
const apiOther = {
|
||||
/**
|
||||
* 发送手机验证码
|
||||
*/
|
||||
sendMobileCode: (params) => {
|
||||
return uni.$u.http.post("/mobile/get_verify_code", {
|
||||
mobileNo: params.mobileNo, // 手机号码
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export default apiOther;
|
||||
@ -0,0 +1,14 @@
|
||||
/**
|
||||
* 站点接口
|
||||
*/
|
||||
|
||||
const apiStations = {
|
||||
/**
|
||||
* 站点列表
|
||||
*/
|
||||
get: (params) => {
|
||||
return uni.$u.http.get("/stations");
|
||||
},
|
||||
};
|
||||
|
||||
export default apiStations;
|
||||
@ -0,0 +1,18 @@
|
||||
/**
|
||||
* 车票接口
|
||||
*/
|
||||
|
||||
const apiTickets = {
|
||||
/**
|
||||
* 车票列表
|
||||
*/
|
||||
get: (params) => {
|
||||
return uni.$u.http.get("/tickets", {
|
||||
params: {
|
||||
state: 1, // 已出票(订单已支付)
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export default apiTickets;
|
||||
@ -0,0 +1,20 @@
|
||||
/**
|
||||
* 车次接口
|
||||
*/
|
||||
|
||||
const apiTrains = {
|
||||
/**
|
||||
* 车次列表
|
||||
*/
|
||||
get: (params) => {
|
||||
return uni.$u.http.get("/trains/query_train", {
|
||||
params: {
|
||||
from: params.from,
|
||||
to: params.to,
|
||||
date: params.date,
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export default apiTrains;
|
||||
@ -0,0 +1,20 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<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="/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@ -0,0 +1,118 @@
|
||||
{
|
||||
"name": "Mini-12306",
|
||||
"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_MOBILE_NO_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": {
|
||||
"dSYMs": false
|
||||
},
|
||||
/* SDK配置 */
|
||||
"sdkConfigs": {
|
||||
"ad": {}
|
||||
},
|
||||
"icons": {
|
||||
"android": {
|
||||
"hdpi": "static/app-icon/72x72.png",
|
||||
"xhdpi": "static/app-icon/96x96.png",
|
||||
"xxhdpi": "static/app-icon/144x144.png",
|
||||
"xxxhdpi": "static/app-icon/192x192.png"
|
||||
},
|
||||
"ios": {
|
||||
"appstore": "",
|
||||
"ipad": {
|
||||
"app": "",
|
||||
"app@2x": "",
|
||||
"notification": "",
|
||||
"notification@2x": "",
|
||||
"proapp@2x": "",
|
||||
"settings": "",
|
||||
"settings@2x": "",
|
||||
"spotlight": "",
|
||||
"spotlight@2x": ""
|
||||
},
|
||||
"iphone": {
|
||||
"app@2x": "",
|
||||
"app@3x": "",
|
||||
"notification@2x": "",
|
||||
"notification@3x": "",
|
||||
"settings@2x": "",
|
||||
"settings@3x": "",
|
||||
"spotlight@2x": "",
|
||||
"spotlight@3x": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"splashscreen": {
|
||||
"androidStyle": "default",
|
||||
"android": {
|
||||
"hdpi": "static/splashscreen/hdpi/_20240628103445.9.png",
|
||||
"mdpi": "static/splashscreen/mdpi/_20240628103445.9.png",
|
||||
"xhdpi": "static/splashscreen/xhdpi/_20240628103445.9.png",
|
||||
"xxhdpi": "static/splashscreen/xxhdpi/_20240628103445.9.png",
|
||||
"xxxhdpi": "static/splashscreen/xxxhdpi/_20240628103445.9.png"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
/* 快应用特有相关 */
|
||||
"quickapp": {},
|
||||
/* 小程序特有相关 */
|
||||
"mp-weixin": {
|
||||
"appid": "",
|
||||
"setting": {
|
||||
"urlCheck": false
|
||||
},
|
||||
"usingComponents": true
|
||||
},
|
||||
"mp-alipay": {
|
||||
"usingComponents": true
|
||||
},
|
||||
"mp-baidu": {
|
||||
"usingComponents": true
|
||||
},
|
||||
"mp-toutiao": {
|
||||
"usingComponents": true
|
||||
},
|
||||
"uniStatistics": {
|
||||
"enable": false
|
||||
},
|
||||
"vueVersion": "2"
|
||||
}
|
||||
@ -0,0 +1,26 @@
|
||||
{
|
||||
"name": "Mini-12306",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"dependencies": {
|
||||
"dayjs": "^1.11.11",
|
||||
"uview-ui": "^2.0.36"
|
||||
}
|
||||
},
|
||||
"node_modules/dayjs": {
|
||||
"version": "1.11.11",
|
||||
"resolved": "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.11.tgz",
|
||||
"integrity": "sha512-okzr3f11N6WuqYtZSvm+F776mB41wRZMhKP+hc34YdW+KmtYYK9iqvHSwo2k9FEH3fhGXvOPV6yz2IcSrfRUDg=="
|
||||
},
|
||||
"node_modules/uview-ui": {
|
||||
"version": "2.0.36",
|
||||
"resolved": "https://registry.npmmirror.com/uview-ui/-/uview-ui-2.0.36.tgz",
|
||||
"integrity": "sha512-ASSZT6M8w3GTO1eFPbsgEFV0U5UujK+8pTNr+MSUbRNcRMC1u63DDTLJVeArV91kWM0bfAexK3SK9pnTqF9TtA==",
|
||||
"engines": {
|
||||
"HBuilderX": "^3.1.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,6 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"dayjs": "1.11.11",
|
||||
"uview-ui": "2.0.36"
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,136 @@
|
||||
<template>
|
||||
<view class="p-page">
|
||||
<u-navbar
|
||||
:placeholder="true"
|
||||
bg-color="transparent"
|
||||
:auto-back="true"
|
||||
></u-navbar>
|
||||
|
||||
<view class="mt-llg p-base u-flex u-flex-column u-flex-items-center">
|
||||
<u-image
|
||||
src="@/static/logo.png"
|
||||
:width="60"
|
||||
:height="60"
|
||||
:radius="10"
|
||||
></u-image>
|
||||
<view class="mt-sm">
|
||||
<u-text text="欢迎登录" :size="20"></u-text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="p-base">
|
||||
<u-form
|
||||
label-position="top"
|
||||
labelWidth="auto"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
ref="formRef"
|
||||
>
|
||||
<u-form-item prop="account">
|
||||
<u-input
|
||||
v-model="formData.account"
|
||||
:custom-style="{ backgroundColor: 'white' }"
|
||||
@focus="$refs.formRef.clearValidate('account')"
|
||||
>
|
||||
<view slot="prefix" class="u-flex-y-center">
|
||||
<view class="ms-base me-sm">用户</view>
|
||||
<u-line
|
||||
direction="col"
|
||||
length="26px"
|
||||
:style="{ margin: '0 var(--space-base)' }"
|
||||
></u-line>
|
||||
</view>
|
||||
</u-input>
|
||||
</u-form-item>
|
||||
<u-form-item prop="password">
|
||||
<u-input
|
||||
v-model="formData.password"
|
||||
type="password"
|
||||
:custom-style="{ backgroundColor: 'white' }"
|
||||
@focus="$refs.formRef.clearValidate('password')"
|
||||
>
|
||||
<view slot="prefix" class="u-flex-y-center">
|
||||
<view class="ms-base me-sm">密码</view>
|
||||
<u-line
|
||||
direction="col"
|
||||
length="26px"
|
||||
:style="{ margin: '0 var(--space-base)' }"
|
||||
></u-line> </view
|
||||
></u-input>
|
||||
</u-form-item>
|
||||
</u-form>
|
||||
|
||||
<view class="mt-llg">
|
||||
<u-button type="primary" @tap="handleLogin">登录</u-button>
|
||||
</view>
|
||||
<view class="mt-base">
|
||||
<u-button @tap="handleToRegister">注册</u-button>
|
||||
</view>
|
||||
|
||||
<view class="mt-llg text-center text-primary">
|
||||
<text :style="{ fontSize: '14px' }">《服务条款》《隐私权政策》</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapActions } from "vuex";
|
||||
import { validateRequired } from "@/utils/form-validators";
|
||||
import apiAuth from "@/api/auth";
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
formData: {
|
||||
account: "",
|
||||
password: "",
|
||||
},
|
||||
formRules: {
|
||||
// 登录时不用 trigger 触发验证时为了交互流畅
|
||||
account: {
|
||||
validator: (rule, value, callback) =>
|
||||
validateRequired(rule, value, callback, { label: "用户名" }),
|
||||
},
|
||||
password: {
|
||||
validator: (rule, value, callback) =>
|
||||
validateRequired(rule, value, callback, { label: "密码" }),
|
||||
},
|
||||
},
|
||||
loading: false,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
...mapActions({ updateAuth: "updateAuth" }),
|
||||
handleLogin() {
|
||||
this.$refs.formRef
|
||||
.validate()
|
||||
.then(() => {
|
||||
uni.showLoading({ title: "正在登录", mask: true });
|
||||
|
||||
apiAuth
|
||||
.login({
|
||||
account: this.formData.account,
|
||||
password: this.formData.password,
|
||||
})
|
||||
.then((res) => {
|
||||
uni.hideLoading();
|
||||
this.updateAuth(res.data);
|
||||
uni.switchTab({
|
||||
url: "/pages/HomeView/HomeView",
|
||||
});
|
||||
})
|
||||
.catch(() => uni.hideLoading());
|
||||
})
|
||||
.catch();
|
||||
},
|
||||
handleToRegister() {
|
||||
uni.navigateTo({
|
||||
url: "/pages/RegistrationView/RegistrationView",
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
@ -0,0 +1,59 @@
|
||||
<template>
|
||||
<u-index-list :index-list="stationList.indexList">
|
||||
<template v-for="(item, index) in stationList.itemArr">
|
||||
<u-index-item>
|
||||
<u-index-anchor :text="stationList.indexList[index]"></u-index-anchor>
|
||||
<view
|
||||
v-for="(cell, i) in item"
|
||||
@tap="handleSelectedStation(cell)"
|
||||
class="py-base mx-base"
|
||||
:class="[{ 'u-border-bottom': i !== item.length - 1 }]"
|
||||
>{{ cell }}</view
|
||||
>
|
||||
</u-index-item>
|
||||
</template>
|
||||
</u-index-list>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from "vuex";
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
key: "",
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState({ stations: "stations" }),
|
||||
stationList() {
|
||||
// updateStations 中已对 stations 进行排序
|
||||
const indexList = [];
|
||||
const itemArr = [];
|
||||
for (const station of this.stations) {
|
||||
const currChar = indexList.slice(-1)[0];
|
||||
const char = station.pinyin[0];
|
||||
if (currChar === char) {
|
||||
itemArr.slice(-1)[0].push(station.name);
|
||||
} else {
|
||||
indexList.push(char);
|
||||
itemArr.push([station.name]);
|
||||
}
|
||||
}
|
||||
return { indexList, itemArr };
|
||||
},
|
||||
},
|
||||
onLoad(query) {
|
||||
const { key } = query;
|
||||
this.key = key;
|
||||
},
|
||||
methods: {
|
||||
handleSelectedStation(stationName) {
|
||||
const pages = getCurrentPages();
|
||||
const prevPage = pages[pages.length - 2];
|
||||
prevPage.$vm[this.key] = stationName;
|
||||
uni.navigateBack({ delta: 1 });
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@ -0,0 +1,87 @@
|
||||
<template>
|
||||
<view v-if="nextTicket" class="page-box TrainItem lg">
|
||||
<view class="px-base py-llg">
|
||||
<view>
|
||||
<u-row :gutter="12">
|
||||
<u-col :span="4">
|
||||
<view class="Time">{{
|
||||
nextTicket.fromTime | timeFormat("HH:mm")
|
||||
}}</view>
|
||||
<view class="Station">{{ nextTicket.from }}</view>
|
||||
</u-col>
|
||||
<u-col :span="4">
|
||||
<view class="text-center">
|
||||
<view class="my-ssm">{{ nextTicket.trainNo }}</view>
|
||||
<u-line></u-line>
|
||||
<view
|
||||
class="my-ssm text-center text-info"
|
||||
:style="{ fontSize: '15px' }"
|
||||
>{{ $u.dayjs(nextTicket.date).format("MM月DD日") }} {{
|
||||
$u.dayjs(nextTicket.date).isToday()
|
||||
? "今天"
|
||||
: $u.timeFormat(nextTicket.date, "[周]dd")
|
||||
}}</view
|
||||
>
|
||||
</view>
|
||||
</u-col>
|
||||
<u-col :span="4">
|
||||
<view class="Time text-end">{{
|
||||
nextTicket.toTime | timeFormat("HH:mm")
|
||||
}}</view>
|
||||
<view class="Station text-end">{{ nextTicket.to }}</view>
|
||||
</u-col>
|
||||
</u-row>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view v-else-if="loading" class="u-flex-xy-center p-base">
|
||||
<text class="text-info">加载中...</text>
|
||||
</view>
|
||||
<view v-else class="u-flex-xy-center p-base">
|
||||
<text class="text-info">当前没有出行计划</text>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from "vuex";
|
||||
import apiTickets from "@/api/tickets";
|
||||
import { timeAscCompareFn } from "@/utils/functions.js";
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
nextTicket: undefined,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState({ auth: "auth" }),
|
||||
},
|
||||
mounted() {
|
||||
this.loadMyTickets();
|
||||
},
|
||||
methods: {
|
||||
loadMyTickets() {
|
||||
this.loading = true;
|
||||
apiTickets
|
||||
.get()
|
||||
.then((res) => {
|
||||
this.nextTicket = res.data
|
||||
.sort(
|
||||
(a, b) =>
|
||||
timeAscCompareFn(a.fromTime, b.fromTime) ||
|
||||
timeAscCompareFn(a.toTime, b.toTime) ||
|
||||
a.id - b.id
|
||||
)
|
||||
.find((tkt) => uni.$u.dayjs().isBefore(uni.$u.dayjs(tkt.toTime)));
|
||||
})
|
||||
.catch(() => {
|
||||
this.nextTicket = undefined;
|
||||
})
|
||||
.then(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 6.2 KiB |
|
After Width: | Height: | Size: 9.7 KiB |
|
After Width: | Height: | Size: 762 B |
|
After Width: | Height: | Size: 502 B |
|
After Width: | Height: | Size: 978 B |
|
After Width: | Height: | Size: 734 B |
|
After Width: | Height: | Size: 883 B |
|
After Width: | Height: | Size: 841 B |
|
After Width: | Height: | Size: 454 B |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 510 B |
|
After Width: | Height: | Size: 525 B |
|
After Width: | Height: | Size: 800 B |
|
After Width: | Height: | Size: 652 B |
|
After Width: | Height: | Size: 579 B |
|
After Width: | Height: | Size: 944 B |
|
After Width: | Height: | Size: 9.2 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 7.9 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 578 B |
|
After Width: | Height: | Size: 594 B |
|
After Width: | Height: | Size: 7.9 KiB |
|
After Width: | Height: | Size: 4.6 KiB |
|
After Width: | Height: | Size: 7.6 KiB |
|
After Width: | Height: | Size: 7.5 KiB |
|
After Width: | Height: | Size: 9.0 KiB |
|
After Width: | Height: | Size: 6.1 KiB |
|
After Width: | Height: | Size: 366 B |
|
After Width: | Height: | Size: 558 B |
|
After Width: | Height: | Size: 186 B |
|
After Width: | Height: | Size: 294 B |
|
After Width: | Height: | Size: 7.2 KiB |
|
After Width: | Height: | Size: 8.4 KiB |
|
After Width: | Height: | Size: 8.5 KiB |
|
After Width: | Height: | Size: 8.2 KiB |
|
After Width: | Height: | Size: 5.1 KiB |
|
After Width: | Height: | Size: 4.6 KiB |
|
After Width: | Height: | Size: 7.2 KiB |
|
After Width: | Height: | Size: 5.5 KiB |