ESEE和eye301项目小记
eye301
25.7.23
一个pycharm使用小技巧
也是Django后端启动命令python manage.py runserver的一个类似快捷方式的技巧。
- 先在界面左侧的
manage.py右键点击run,跑完之后,上边栏的绿三角run就会出现manage的图标。 - 点击该图标,在其中找到
Edit Configurations并点击。 - 随后在打开的界面的左侧中找到
Python下的manage并点击,随后在右侧的Script parameters中输入参数runserver即可使之后每次对该文件run时,带上该参数进行,效果大致如下:
25.7.24
25.7.29
now = timezone.make_aware(datetime.now(timezone.utc))语句中now的类型?datetime.now(timezone.utc)获得一个表示当前 UTC 时间的aware对象(有时区信息)timezone.make_aware(...)将naive datetime对象转换为aware datetime对象(附加时区信息)
过于冗长了,可以更加简洁:now = datetime.now(timezone.utc)或now = timezone.now()- 看一下数据库
create等操作中default=的含义是什么?create方法中并没有default参数,除非是有定义该字段,否则建议还是按字典赋值的方式赋值即:key = value;但是对于update_or_create,其含义是,拿传入的非defaults的字段的值去检索数据库中对应的一行数据,相当于不在defaults中的字段就是主键,而之后再拿defaults中的字段去更新或创建对应的一行数据中的相应字段,即有则改之,无则创建。 Response改成JsonResponse
已解决- 成功执行后需要返回数据给前端,尤其是
get请求
已解决 - 单列一个
view文件存PostOperationInfoView的内容
已解决 - Munk评级的中文含义
已解决 - Django是否有一次性输入数据的操作来替代
for循环遍历bulk_create()来批量插入数据库
优点:
速度非常快: 相比于循环创建和保存,性能提升显著。
代码简洁。
缺点:
不触发信号: 不会触发 pre_save 或 post_save 信号。
不调用 save() 方法: 不会执行模型中定义的 save() 方法的逻辑。
不支持 ManyToMany 字段的批量创建: 需要手动处理ManyToMany关系的创建 - 测试
1)修改时会出现此前没有的存在的数据条目嘛?就是这病人的某一个眼别和术后随访类别组合不存在,
如果可以的话,应该是直接报错还是填入数据库中?
25.8.1
服务器内部可能错误
- 传入字段不存在
- 传入字段的类型不符合设置的,例如:
BigInteger传给Integer - 注意可以使用
try .... catch来捕获异常,而不是傻傻的盯着服务器异常看(
25.8.3
手术信息、病人信息修改等事宜
- 手术信息和手术其他信息一起按数组的形式传给后端
- 病史、既往病史、诊断做成纵表,需要像手术信息那样把其他单拎一个出来表
- 将与眼别无关的状态置为2
- 术前评分是横表
- 上述表全部重新建表
25.8.4
既往史相关信息的修正
也是做成纵表的形式,用”100”这种每一位代表每一层的方式来表示不同字段的组合:
具体的代码形式(我认为的)如下:
1 | |
25.8.7
- 纵表的其他内容是”可有可无”的,即只有需要有其他内容才应该要有,否则应该闲置。而对于像某些症状需要补充内容的情况,则跟“其他,请文字输入”一致,都是主表存在并在副表根据主表的主键号来设置关联,不过对于其他的这种是主表点明有副表内容,否则就是用户提供了才有副表内容。
- 术后随访信息补充:1.术后带管时间,自由输入,单位月;2.术后并发症:多选(泪点撕裂、人工泪管移位、继发性泪小管炎和填空自由输入)。
25.8.9
- 如何进行既往史的数据生成:
- 对横表而言:1000000条眼睛数据中每个病人500000条左眼(0),500000右眼(1)信息,只能先生成500000条左眼,然后再生成500000条右眼,分两次生成,由于横表的多选是要采用varchar类型的,因此还是随机生成,例如对于:
PRE_TEAR_SUR = [(0, "泪道探通"), (1, "泪道置管术"), (2, "外路泪囊鼻腔吻合术"), (3, "经鼻泪囊鼻腔吻合术"), (4, "其他,请文字输入")]
则采用正则表达式:[0-1]/[2-3]/4的方式生成。 - 对纵表而言,由于不考虑那些公有的属性,因此只需要考虑与眼别相关的两个属性,这两个属性每个4项多选(为了方便每个病人的数据数相同),因此一个病人8条单眼数据,则一个病人拥有16条数据,一共62500个病人,分16次录入数据,每个病人按照眼别与与眼别相关属性的选项的组合来生成
- 对横表而言:1000000条眼睛数据中每个病人500000条左眼(0),500000右眼(1)信息,只能先生成500000条左眼,然后再生成500000条右眼,分两次生成,由于横表的多选是要采用varchar类型的,因此还是随机生成,例如对于:
25.8.10
- 对于术后随访信息表增加其他表来存放术后并发症的其他信息。bingo
- 编写接口(好巧妙的方式)用于针对既往史表的600万条数据(50万个病人信息,100万只眼睛的信息,每只眼睛6条信息) bingo
- 针对2的接口生成用于自动化发送请求的python脚本,问问gpt bingo
- 数据生成完后将建立相应的索引把查询速度对比记录在一张表中
25.8.11
- 在数据库中对于以正则表达式建立的查询在多次同一查询下速度越来越快的可能原因是?
1). 查询缓存,在语句本身结构和表达式本身并未发生变化的情况下仍可能发生作用。
2). 正则表达式引擎缓存,避免重复编译。
3). 预热效应,第一次执行时进行的诸如加载数据等初始化操作在后续执行时可能会被跳过,从而提高查询速度。 - 但是对于字段相等的查询则每次的查询时间都有所波动,这可能是什么原因导致的,例如:select * FROM patientinfo_originalpastmedicalhistory WHERE ocular_disease_history REGEXP “1/*”;SELECT * FROM patientinfo_originalpastmedicalhistory WHERE patient_id = “66666”;例如在使用navicat反复执行这两个语句时第一条语句的查询时间是越来越短,但是第二条语句的查询时间则是来回波动。
- 如何处理数据库中纵表的数据,例如一条数据由玩家id、属性(1、2)、属性名称(对于属性1有1、2、3个名称、对于属性2有4、5、6个名称)组成,请问我应该如何编写mysql语句来实现查询同时拥有属性1的1和2名称的玩家数据?
1). 使用自连接(已知属性名称id数的情况下),此方法将表自身连接多次来查找满足所有条件的玩家,例如:3).使用子查询,但是可能性能较差1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33-- 选择所有满足(4, 1)并且其存在有(4, 3)记录条件的记录,
SELECT *
FROM patientinfo_pastmedicalhistory AS pa1
WHERE pa1.past_medical_history_att = 4
AND pa1.past_medical_history_att_content = 1
AND EXISTS (SELECT 1
FROM patientinfo_pastmedicalhistory AS pa2
WHERE pa2.patient_id = pa1.patient_id
AND pa2.past_medical_history_att = pa1.past_medical_history_att
AND pa2.past_medical_history_att_content = 3)
-- UNION去重连接,将上下两个查询结果去重合并
UNION
-- 选组满足(4, 3)且存在(4, 1)条件的记录
SELECT *
FROM patientinfo_pastmedicalhistory AS pa2
WHERE pa2.past_medical_history_att = 4
AND pa2.past_medical_history_att_content = 3
AND EXISTS (SELECT 1
FROM patientinfo_pastmedicalhistory AS pa1
WHERE pa1.patient_id = pa2.patient_id
AND pa1.past_medical_history_att = pa2.past_medical_history_att
AND pa1.past_medical_history_att_content = 1);
```
2).使用GROUP BY 和 HAVING语句
例如:
```sql
-- 此处若想查询所有信息即*,可能会遇到sql_mode=only_full_group_by 强制要求 SELECT 语句中除了聚合函数(例如 COUNT(), SUM(), AVG() 等)以外的所有列,都必须出现在 GROUP BY 子句中,或者在功能上依赖于 GROUP BY 子句中的列。 换句话说,SELECT 列表中的非聚合列必须是唯一的,由 GROUP BY 列决定的情况,因此更推荐使用子查询的方式即嵌套查询,先查出id然后根据查出来的id查询所有信息
SELECT player_id
FROM player_attributes
WHERE attribute_id = 1
AND attribute_name_id IN (1, 2)
GROUP BY player_id
HAVING COUNT(DISTINCT attribute_name_id) = 2;对于大型表来说,方法二较为合适,若查找属性名称id数固定且数量较少使用方法一,方法三适合在可读性要求高且性能不是关键考虑的情况下考虑。1
2
3
4
5
6SELECT DISTINCT player_id
FROM player_attributes
WHERE attribute_id = 1
AND attribute_name_id IN (1, 2)
AND player_id IN (SELECT player_id FROM player_attributes WHERE attribute_id = 1 AND attribute_name_id = 1)
AND player_id IN (SELECT player_id FROM player_attributes WHERE attribute_id = 1 AND attribute_name_id = 2); - Django框架中的
xxx.objects.create()方法不接受模型对象,其目的是创建一个新的模型实例并保存到数据库中,接收一个字典作为参数,键值对对应于模型字段名和值,可以直接输入键值对的形式如create(XXX=xxx,...)
当然也可以将键值对也输入字典dict = {'':'', ...}中,然后使用展开字典:create(**dict)。
那么为什么直接传入模型的构造实例的方法又可以了,例如:XXX.objects.create(XXX()),这是因为XXX()创建了一个模型实例但是,并未放入数据库中,虽然 create() 方法本身不接受模型实例,但 Django ORM 在这种情况下会进行特殊处理。它会读取传入模型实例的所有字段的值,并将这些值作为关键字参数传递给内部的 create() 方法。(不过这种方法不推荐使用,相当于内存和数据库中都存入了一模一样的实例,浪费空间)**
25.8.12
- 经过比较横纵表的查询效率后又将原来从横表改为纵表又改回横表了(
- 主表的其他表对于主表中多个多选需要填入的情况存在局限性,而但是如果将主表中所有的需要输入的字段编号并在其他表中通过属性与属性内容一一对应的方式,即可达到存储的效果,例如:属性:”1/3/5”;属性内容:”xxx/yyy/zzz”
- 将所有其他信息表的纵表形式加入主表中
25.8.17
isinstance()是用来检查一个对象是否为某个类的实例的,要使该函数返回值为True,需要传递一个类的实例作为第一参数,传入类本身反而会返回False;那么该如何判断传入的参数是否为某个类本身呢?使用type(obj) is type的方法,前者type(),对于类来说,type(obj) 将返回 <class ‘type’>。 对于类的实例来说,type(obj) 将返回该类本身(例如,<class ‘__main__.MyClass’>),在Python中,类本身也是对象,它们的类型是type。 因此,type(obj) is type用于检查obj的类型是否是type,即它是否是一个类。但是其并不能够去判断其是否是某个特定的类,应该使用如下方式来解决:model is Class来进行判断。这里需要注意is是对象标识判断,也即一定需要两者指向同一内存地址空间才会返回True,因此对于type(model) is Class中当model也是Class时,前者与后者值相同,但是位于不同的内存地址上,因此会返回False。
25.8.18
python中使用字典调用函数的时候需要注意不同函数之间对于同一个参数的类型限制需要统一:
例如:其中,若在某几个函数中并不需要1
2
3
4
5
6
7
8
9switch_dict = {
0: self.fill_case0,
1: self.fill_case1,
2: self.fill_case2,
3: self.fill_case3,
4: self.fill_case4,
}
func = (switch_dict.get(type, self.fill_default))
func(isFirst, num, tempDict, item)num可以相加减计算而另一个函数中则需要,那么这两个函数调用在对于num的类型限制上就会有所不同,而编译器察觉到此就会报警告。
解决方案是可以在函数声明处,显示的标明类型:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24def fill_case0(self, isFirst: bool, num: int, tempDict: dict, item: dict):
pass
def fill_case1(self, isFirst: bool, num: int, tempDict: dict, item: dict):
pass
def fill_case3(self, isFirst: bool, num: int, tempDict: dict, item: dict):
# 由于num声明为int,支持__ge__、__sub__,与其他方法一致
pass
def fill_default(self, isFirst: bool, num: int, tempDict: dict, item: dict):
pass
def fill_data(self, isFirst: bool, num: int, tempDict: dict, item: dict, type: int):
switch_dict = {
0: self.fill_case0,
1: self.fill_case1,
2: self.fill_case2,
3: self.fill_case3,
4: self.fill_case4,
}
func = switch_dict.get(type, self.fill_default)
# 此时参数类型与所有方法的声明一致,警告消除
return func(isFirst, num, tempDict, item)- Django的serializer限制可以对于进行一些比较复杂的限制嘛?例如:对于CharField类型的字段att需要存在如下限制:“x/y/z”(其中x、y、z都是整数且都满足一定的取值范围例如[0,5]之间)?
可以使用自定义字段验证器和重写序列化器的验证方法。- 通过重写(
validate_<字段名>),在一个函数中完成验证(其中的value是前端提供的原始数据):
例如:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62from rest_framework import serializers
class MySerializer(serializers.Serializer):
att = serializers.CharField()
def validate_att(self, value):
"""验证 att 字段是否符合 "x/y/z" 格式,且 x,y,z 在 [0,5] 之间"""
# 1. 检查格式是否为 "x/y/z"(分割后有 3 个部分)
parts = value.split('/')
if len(parts) != 3:
raise serializers.ValidationError("格式必须为 'x/y/z'(x、y、z 为整数)")
# 2. 检查 x,y,z 是否为整数
try:
x, y, z = map(int, parts)
except ValueError:
raise serializers.ValidationError("x、y、z 必须为整数")
# 3. 检查 x,y,z 是否在 [0,5] 之间
for num in [x, y, z]:
if not (0 <= num <= 5):
raise serializers.ValidationError("x、y、z 必须在 0-5 之间")
# 验证通过,返回原始值
return value
```
2. 若涉及多个字段的验证,那么可以重写`validate`方法,进行对象级验证,(其中的`data`是经过字段级验证后的所有字段组成的字典,可以任意调用这些验证成功后的字段,进而进行不同字段之间的限制规范):
例如:
```python
from rest_framework import serializers
from datetime import date
class MySerializer(serializers.Serializer):
start_date = serializers.DateField()
end_date = serializers.DateField()
min_age = serializers.IntegerField()
max_age = serializers.IntegerField()
def validate(self, data):
"""对象级验证:处理多个字段的关联逻辑"""
# 1. 从 data 字典中获取需要验证的多个字段
start_date = data.get('start_date') # 获取 start_date 字段的值
end_date = data.get('end_date') # 获取 end_date 字段的值
min_age = data.get('min_age')
max_age = data.get('max_age')
# 2. 验证 start_date 和 end_date 的关系
if start_date and end_date and start_date > end_date:
# 抛出错误时,可指定错误对应的字段(如 'end_date')
raise serializers.ValidationError({
'end_date': '结束日期不能早于开始日期'
})
# 3. 验证 min_age 和 max_age 的关系
if min_age is not None and max_age is not None and min_age > max_age:
raise serializers.ValidationError({
'max_age': '最大年龄不能小于最小年龄',
'min_age': '最小年龄不能大于最大年龄' # 可同时指定多个字段的错误
})
# 4. 验证通过后,必须返回完整的 data 字典(可修改后返回)
return data
- 通过重写(
25.8.19
hasattr和setattr的用法。- POST 接口的 Body 中,DateTime 类型数据必须以字符串(string)形式传递,核心是确保字符串格式符合后端要求。只要格式正确,后端框架会自动完成字符串到日期时间对象的解析。
25.8.21
- 去除术后随访信息表中的重复并发症部分。
serializer.errors包含Django进行的序列化器验证下验证数据失败后所有验证不通过的字段和错误愿意,以字典的格式返回,键为字段名,值为错误信息(字符串或列表),对非字段错误,信息会放在non_field_errors键下,可以在序列化器定义的时候通过error_messages自定义错误提示,例如:1
2
3
4
5
6
7
8
9
10
11
12class OperationInfoSerializer(serializers.ModelSerializer):
name = serializers.CharField(
max_length=100,
error_messages={
"required": "手术名称不能为空",
"max_length": "手术名称不能超过100个字符"
}
)
class Meta:
model = OperationInfo
fields = ['id', 'name', 'date', 'doctor']serializer定义的字段若没有设定是否可以为空或为null(allow_blank和allow_null),则默认都不可,需要手动设定可以。- 手术信息表改成横表,增加入院时间字段
- 多进行调试,
25.8.22
- 直接将本地表同服务器表进行同步,不要直接在服务器上执行迁移命令
- 检索和分析:检索:时间范围、术后时间;分析:根据筛选范围筛选所有符合条件病人,保存用户病人id和眼别的集合,get接口获取病人集合接口
25.8.23
- 获得页数的代码按照豆包的润色进行修改
- 修改更新接口的逻辑,更新时前端会将所有数据都回传给你,不用担心传空的情况。
- 对于上传接口可以充分利用序列化器,使用其
save()方法,通过传递序列化器定义时未曾赋予的额外的字段来保存数据;异常捕获范围越仔细越好;采用日志记录来记录报错信息;使用transaction.atomic()来确保操作的原子性,返回创建的实例数据而非原始的数据会更加安全 - 更新接口可以使用序列化器的”更新模式”(传入
instance参数,而非仅用data);直接使用update()方法来更新已有记录(此处的update方法针对的是查询集,其会批量更新查询集中所有的对象,性能更优,但不会触发save()方法,而不是单个模型实例);直接基于更新数据补充不修改或未提供等字段,便于维护;缺少异常处理和日志记录。 - 修改了近乎所有的上传和更新接口
- 图片文件的处理:通过
os库提供的路径拼接并创建相应目录,对于两个数组按照索引形成映射关系的模式可以按如下方式进行遍历:此后通过如下方式生成唯一文件名:1
2# image_files和image_types为数组
for idx , (img, img_type) in enumerate(zip(image_files, image_types)):通过如下方式进行文件的保存(记得要try-catch):1
2
3
4timestamp = int(time.time() * 1000)
unique_str = str(uuid.uuid4())[:8]
# img_type为文件类型
saved_name = f"{img_type}_{timestamp}_{unique_str}{ext}"其中1
2
3with open(img_path, "wb") as fp:
for chunk in img.chunks():
fp.write(chunk)img.chunks()会将文件内容分割成多个小片段,对于大文件来说,可避免一次性占用过多内存,是Django中处理上传文件的标准写法,分块迭代的写入磁盘,保证大文件处理时内存安全同时兼顾写入效率。
25.8.24
- Django中目前有三个字段的值但是其中可能有None,需要检索模型BaseInfo的表中的这三个字段联合索引下的信息,应该如何高效的编写代码?
查询时用isnull条件匹配值为None的字段。大致方法如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23# 动态构建查询参数
query_kwargs = {}
# 处理field1(值为None时用isnull=True,否则直接匹配值)
if v1 is None:
query_kwargs['field1__isnull'] = True
else:
query_kwargs['field1'] = v1
# 处理field2
if v2 is None:
query_kwargs['field2__isnull'] = True
else:
query_kwargs['field2'] = v2
# 处理field3
if v3 is None:
query_kwargs['field3__isnull'] = True
else:
query_kwargs['field3'] = v3
# 执行查询(利用联合索引,效率最高)
return BaseInfo.objects.filter(**query_kwargs) - 对于通过某一字段外键连接在一起的两张表,可以使用
__来进行“跨关联访问字段”:关联名称__字段名,因此treatments__duct等价于通过treatments关联找到Treatment模型的duct字段,则Q(treatments__duct=d)的作用是:通过关联关系筛选出 “有符合条件的Treatment记录” 的Patient。 - Django为了区分”关联对象”和”关联id”(即“外键关联的字段名”和“实际存储的关联id值”,因为模型可以通过外键字段直接获取到完整的关联对象:
treatment.patient_id,但是在数据库中存储的只是关联表的主键id值,而不是完整的关联对象。),这可以避免混淆对象访问和id存储,使在多表关联查询时,逻辑更为清晰。但是如果对于本身就带有_id后缀的字段,这样或许会发生冲突,存储的字段名将变为patient_id_id,这显然不是我们相要得到的,因此可以在定义的时候手动指定数据库列名:db_column=”…” - 通过外键相连接的两个字段的类型一定要相同,可以通过如下
sql命令来查看:show INDEX FROM table_name,包括但不限于类型相同,都是char类型的话要字符集和排序规则也都相同。 django.db.utils.IntegrityError: (1452, 'Cannot add or update a child row: a foreign key constraint fails (eye301.#sql-1b14_47, CON STRAINTpatientInfo_medicalh_patient_id_d2c054ad_fk_patientInFOREIGN KEY (patient_id) REFERENCESpatientinfo_baseinfo(patient_id))')此类错误的,可能原因是由于通过外键连接的子表中存在主表中不存在的关联字段值,导致发生冲突。
25.8.25
- 错误太多太难处理了,只好重删表了,重新迁移
25.8.26
- 论文真的又多又杂,难搞啊:(
25.8.27
list.append() takes no keyword arguments的错因是:**调用列表的append()方法时使用了关键字参数,但该方法只接受位置参数,不支持关键字参数(如key=value形式)**。因此,不能直接向列表中直接传入键值对,因为键值对就是关键字参数的形式。Django中,bulk_create(objs, batch_size=None, ignore_conflicts=False)用于批量创建数据库记录,其核心参数是未保存的模型实例列表,可高效插入多条记录的场景。
25.8.30
- 注意
Q对象的筛选只能筛选字符串(__contains、__icontains),因为匹配的是字符串中的字符,或者可以使用__regex(正则表达式)。 - 注意
python除法得到的将会是浮点数,而浮点数转换为字符串会带有小数点,例如:3.0->’3.0’(显而易见),因此这样匹配的不是’3’而是’3.0’,所以对于涉及到除法的整数映射关系,需要记得转换为int。
25.9.8
- 在 Python 中使用eval()解析字符串”[{patient_id:1}]”会报错,具体错误为NameError: name ‘patient_id’ is not defined,字符串”[{patient_id:1}]”的语法在 Python 中存在歧义:
从结构上看,它试图表示一个包含字典的列表,类似[{key: value}]。
但字典的键patient_id被视为未定义的变量(而非字符串键)。
在 Python 中,字典的键若为标识符(如patient_id),会被解析为变量名,而不是字符串。由于patient_id未被定义,eval()执行时会找不到该变量,从而抛出NameError。解决方案是将字典的键改为字符串形式。
25.9.10
- 单个集合的查看接口除了需要返回病人id和眼别外,还需要返回病人姓名、性别、出生日期等基本信息,便于用户识别病人。(bingo)
- 对于按照检索词进行检索后的结果,按照每种类型进行比例计算,并返回给前端,便于前端进行饼图等可视化展示。(bingo)
- 对于检索结果中的数字型内容计算平均数和标准差,并返回给前端,便于前端进行折线图等可视化展示,包括年龄、溢泪持续时间、带管时间、Munk评分(术前和术后)等。 (bingo 9.11)
25.9.11
- 鼻炎和肿瘤涉及全身情况,和性别等个人信息按照人统计,其他的检索结果按照眼别统计。
- 对于按照眼别的,若一只眼同时满足一个检索条件的多种情况,例如:若某个病人的某只眼睛同时上下泪小管都断裂那么只会计算一次。
- 对于某种检索情况,应该是当前集合中所有元素中患有该病情的人的占比。
- 对于用户没有填写的字段默认为无来处理。
- 返回数据按’data’、’chart’、’static’进行分层处理。
25.9.12
- 进行smile2023_front-master项目的部署过程中(运行
pnpm run dev命令时)出现了如下的报错:
,询问ai得到 - 最后通过
cursor解决。
25.9.13
1.
1 | |
同样cursor通过清除npm的cache缓存,实现再启动。
再排查原因应该是node.js的版本过低导致的,需要进行手动升级。
25.9.15
- 使用
vs代码片段,即预先定义好的代码模板,举例来说:其中,”Console log” 是这个片段的名字,”prefix” 是触发词,”body” 是实际的代码内容,”description” 是描述。1
2
3
4
5
6
7{
"Console log": {
"prefix": "clog",
"body": ["console.log('$1');", "$2"],
"description": "打印日志到控制台"
}
}
进阶部分:- 占位符和制表符:
$1、…为最简单占位符,插入片段后,光标会先停在$1上,按Tab跳转到$2,以此类推,$0为最后的占位符,代表终点站,可以通过跳转快速实现自定义模板片段,也可以使用默认值,例:${1:defaultValue}或者提供多个选项来选择:${1|option1,option2,option3|}光标跳到的时候会提供多选的方式。 - 还可以使用一些内置变量:
1
2
3
4
5
6
7
8
9
10
11
12$TM_FILENAME: 当前文件的文件名
$TM_FILENAME_BASE: 当前文件的文件名(不含扩展名)
$TM_DIRECTORY: 当前文件所在的目录
$TM_FILEPATH: 当前文件的完整文件路径
$CLIPBOARD: 当前剪贴板中的内容
$WORKSPACE_NAME: 打开的工作区的名称
$CURRENT_YEAR: 当前年份
$CURRENT_MONTH: 当前月份(两位数格式)
$CURRENT_DATE: 当前日期(两位数格式)
$CURRENT_HOUR: 当前小时(24 小时制)
$CURRENT_MINUTE: 当前分钟
$CURRENT_SECOND: 当前秒数 - 还可以进行一系列花式操作,如改变大小写、格式等:
1
2
3
4${VAR/(.*)/${1:/upcase}/}:转为大写
// 小写同理
${VAR/(.*)/${1:/pascalcase}/}:转为帕斯卡命名法
${VAR/(.*)/${1:/camelcase}/}:转为驼峰命名法 - 嵌套占位:例:
1
2
3
4
5
6
7
8
9
10
11
12
13{
"Conditional Statement": {
"prefix": "ifelse",
"body": [
"if (${1:条件}) {",
"\t${2:// 条件成立时的代码}",
"} else {",
"\t${2/^(.*)$/$1/}",
"}"
],
"description": "创建 if-else 语句,自动复制 if 块的注释到 else 块"
}
}
- 占位符和制表符:
25.9.16
- 使用
FastAPI框架来重新对接接口,看看FastAPI能否跟数据库对接。 - 仿照此前项目,调用
echarts来绘画饼图和柱状图。
25.9.17
- 学习并应用
FastAPI到现有后端框架中。 FastAPI框架可以通过SQLALCHEMY来完成与数据库的连接配置,通过SQLALCHEMY_DATABASE_URI并运用create_engine来创建数据库引擎,最后使用sessionmaker来创建数据库会话。- 测试过程中出现一个潜在的坑,就是由于数据库
URL的格式是用户:密码@服务器/数据库?参数因此若密码中有@,会使解析出错,此时需要使用URL中@的编码%40,而不是直接使用@,会导致解析失败。
25.9.18
- 由于8000端口一直被占用,导致后续重新开的服务,都显示的是占用端口的那个,因此,出现了修改了代码却无法同步服务的情况,这个故事告诉我们,一个服务不用了一定要暂停,遇事不决重启解决()。
- 学长要求使用
FastAPI框架为安卓app来搭建后端,需要建立一个存储学生相关信息的表,其中一共有10项字段(除去自增主键):姓名、年级、班级、学校、上传时间、左眼视力、左眼屈光度、左眼屈光度表视标、右眼视力、右眼屈光度、右眼屈光度表视标。
25.9.19
- 使用如下命令安装某一文件中列出的所有依赖:
pip install -r requirements.txt。 - python中typing包中的Annotated的作用是?
其是一个特殊的类型提示工具,用于为类型添加额外的元数据,在保留原始类型信息的同时附加一些可被工具、库或框架读取并利用的辅助性元数据。
语法:Annotated[type, metadata1, metadata2, ...],其中type为原始类型,后续参数为元数据,用于附加额外信息。
例如:Age = Annotated[int, "年龄必须大于 0", {"min": 1, "max": 120}](元数据可以是对类型的说明、约束、业务含义等)
还可与相应框架相联系:1
2
3@app.get("/items/")
async def read_items(q: Annotated[str | None, Query(max_length=50)] = None): # 请求时会验证参数长度
return {"q": q} python中yield关键字的作用:用于生成器函数,作用是返回一个值并暂停函数执行,同时保留函数当前状态,下次调用生成器时,会从暂停的位置继续执行,直到再次遇到yield或函数结束。包含该关键字的函数是关键字函数,不会立即执行函数体,而是返回一个生成器对象,通过调用生成器的__next__()方法或使用next()函数才可触发函数执行。例子:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31def number_generator():
yield 1 # 第一次调用 next() 时返回 1,暂停
yield 2 # 第二次调用 next() 时返回 2,暂停
yield 3 # 第三次调用 next() 时返回 3,暂停
# 调用生成器函数,返回生成器对象(不执行函数体)
gen = number_generator()
print(next(gen)) # 执行到第一个 yield,返回 1
print(next(gen)) # 从上次暂停处继续,执行到第二个 yield,返回 2
print(next(gen)) # 继续执行,到第三个 yield,返回 3
print(next(gen)) # 函数已无更多 yield,抛出 StopIteration 异常
# 当然此处由于生成器对象是可迭代对象,因此可以直接用于for循环,而不用手动调用next()
# 此外还可以通过send()向生成器传值,实现"双向通信",此时yield既输出又输入。
def accumulator():
total = 0
while True:
# 暂停并返回 total,同时接收外部传入的值(首次调用需传 None)
num = yield total
if num is None:
break
total += num
# 创建生成器
acc = accumulator()
# 首次调用需先启动生成器(传 None),因为此时函数内一行都未执行,
# 此处执行后,函数暂停在第一次的yield total,此时怒骂并未被赋值
print(acc.send(None)) # 输出 0(执行到 yield,返回初始 total=0)
# 此后将会把值传递给上次暂停的yield表达式即赋给num,以此类推
print(acc.send(5)) # 传入 5,total 变为 5,返回 5
print(acc.send(3)) # 传入 3,total 变为 8,返回 8
print(acc.send(2)) # 传入 2,total 变为 10,返回 10
acc.send(None) # 传入 None,退出循环,生成器结束
# 代码的本质是利用了生成器的暂停-恢复机制,实现可交互的累加器,节省内存且支持动态行为。- 关于
FastAPI我的建议是,完全按照官方文档来,主要问题是在于只有返回Pydantic模型,才可以正常解析成json格式,其他的没有办法正常解析。
25.9.22
0.0.0.0,localhost(127.0.0.1)和服务器IP的区别:0.0.0.0:所有”网络接口”的占位符,不是一个具体的地址,表示”监听服务器上所有可用的网络接口”,当绑定某一端口时,会同时监听服务器的内网IP,外网IP和localhost等所有网卡的端口,因此,外部用户可通过服务器端的”主机号”访问此服务.- localhost为本地回环地址,仅指向服务器本身的”本地回环接口”,只能在服务器内部访问,外部用户无法通过服务器的”主机号”访问此服务,只能通过服务器本身访问.
- 服务器IP:具体网卡地址,是服务器物理网卡上绑定的实际IP,若服务绑定到某一端口,系统会尝试将其绑定到该IP对应网卡上,若IP未被识别则会报”无法分配地址”的错误,一般来说使用
0.0.0.0时,服务会自动绑定到服务器所有有效网卡,而给出具体IP不一定有对应的网卡就不能正常分配地址.
25.9.23
- 在
Ubuntu上使用sudo lsof -i :8081找到了相应的进程,但是这个进程与通过ps -aux| grep 8081可以找到的进程之间的区别是什么呢?为什么后者可能找不到前者找到的进程?查找逻辑不同,前者作用是查找所有打开了8081端口网络连接的进程,通过内核的网络连接表直接关联”端口”和”占用端口的进程”;但后者是“先列出所有进程详细信息”,再筛选出包含相应字符串的进程,因此有可能出现进程信息中不含有”对应字符串”的情况,导致后者无法找到,但是前者可以正常找到。
25.9.24
- 前端数组赋值出错,debug好久,艹。
25.9.25
NaN不属于JSON标准类型,仅包含string、number、boolean、array、object、null6种,此外的类型将会被判定为不合法,只能以string类型接收。可以在后端将NaN转化成符合JSON格式的null,便于处理。
25.9.26
- 添加了关于
泪道内异物的索引,以及相关的统计和筛选操作。 - 如何在vue3框架下组织前端请求,向后端发送形如这7种数组的form-data数据类型?使用
FormData的append方法添加数据,通过循环的方式将数组中每个元素以数组名的形式添加到FormData中,注意还需要进行请求头的设置:将 Content-Type 设置为 multipart/form-data,以告知后端请求体的格式是 form-data:形如:1
2
3
4
5const response = await axios.post('/api/get_patientSetsInfo', formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
}); - 将获取满足一定条件病人的接口有
get改为post,因为后者相较于前者更适合使用表单结构,前者一般将查询信息放置在路径上,不适合使用诸如表单信息这种放置在请求体中的数据。 - 控制台中显示的
FormData对象为空为正常现象,因为其作为一个特殊的对象,控制台不会直接显示其内部数据,只会展示其方法和原型信息,这并不代表你添加的数据并不存在,可以使用如下方式来验证表单中是否正确添加了数据:1
2
3
4console.log("FormData内容:");
for (let [key, value] of formData.entries()) {
console.log(`${key}: ${value}`);
} typescript中要获取Promise中的Object里的数据,需利用异步编程的语法(async/await 或 Promise.then()),等其状态变为fulfilled后再提取结果:const result = await get_baseinfo_by_Name_and_Diagnoses(NAME, diagnose_list);或get_baseinfo_by_Name_and_Diagnoses(NAME, diagnose_list) .then((result) => { // result 是 PromiseResult 里的 Object console.log(result.someProperty); // 替换 someProperty 为实际属性名 })。ts中判断一个元素是否在某个集合中不是使用in,而是使用.has()函数来判断,in是用来判断对象是否拥有某个属性的。
25.9.27
1.
1
2
3
4<pure-table row-key="UUID" :data="dataList" :columns="columns" :header-cell-style="{
background: '#f5f5f5',
height: '32px'
}" />
该代码基于Vue使用表格组件渲染数据表格的配置,主要作用是展示dataList中的数据,并通过指定属性配置表格的基础样式和行为,
其中row-key确保唯一识别每一行数据,用于优化表格的渲染性能。:data通过Vue数据绑定,将数据源传递给表格组件,渲染为表格行数据。:columns:配置表格的列信息,一般为一个数组,每个元素定义一列的属性,其中列名需要和data中的属性名严格对应。
25.9.29
- 如何在两个按钮间添加空格:
- 使用
CSS margin(推荐):<el-button @click="deleteSingleSetData" style="margin-left: 20px;">删除</el-button> - 使用
Element UI的间距类:<el-button @click="deleteSingleSetData" class="ml-20">删除</el-button>,相应的class属性在CSS中设定。 - 使用
flex布局控制间距:<div class="dialog-footer" style="display: flex; gap: 20px; justify-content: flex-end;">(其中包含有两个按钮)。 - 使用空格元素:在两个按钮布局间添加:
<div style="width: 20px;"></div>。 - 使用
Element UI的Row和Col布局1
2
3
4
5
6
7
8
9
10<div class="dialog-footer">
<el-row :gutter="20">
<el-col :span="12">
<el-button @click="examineDialogVisibleDelete = false" style="width: 100%">取消</el-button>
</el-col>
<el-col :span="12">
<el-button @click="deleteSingleSetData" style="width: 100%">删除</el-button>
</el-col>
</el-row>
</div>
- 使用
25.10.1
- 更换
java环境时,需要将%JAVA_HOME%/bin从系统变量中调整至某些键值对之前才可以正常更换(如:C:\Program Files\Common Files\Oracle\Java\javapath)。?原因未知?后续探索一下。 java中使用Files.readString(...)或Files.readAllBytes(path)和Files.writeString(...)或Files.write(...)来从文件路径中读取内容和输出内容至文件路径。
25.10.8
- 如何在vue3中一个dialog弹出时执行某个函数进而使后续的窗口显示相应获取的数据?使用
@open事件,其会在对话框弹出动画结束后触发
25.10.9
- 使用
splice(start, delete, item)(在数组的start处删除delete个元素的方式插入item),注意是在原基础上进行修改的,会修改数组的。
25.10.11
- 在重新筛选后或者重新打开筛选界面,已选择的行应该被剔除。
- 在
Vue3中实现对话框内动态显示按钮并控制其相对位置,可以通过条件渲染结合CSS 定位实现。 - 可以对
pure-table中某一列的值进行转换,通常通过在列配置中使用formatter或cellRenderer(具体属性名取决于组件 API 设计)来实现值的映射转换。例如1
2
3
4formatter: (row) => {
// row为当前行的数据对象,可通过row.eye获取原始值
return row.eye === 0 ? "左眼" : "右眼";
},
25.10.13
- 待办事项:1.眼别列的顺序问题;2.分页工作;3.修改页面的新增病人筛选功能,接入分页工作。4.接入图表。5.查看内查看的左右眼保留问题。
25.10.15
- 1.修改了眼别列的顺序;增加了修改页面的病人筛选功能;增加了分页的功能,使用
computed来实现计算结果。
25.10.16
- 修改逻辑需要进行修改,保存后直接修改成功即可。bingo
- 实现真分页,每一页的数据获取通过重新从后端调用读取,而非一次性将数据存在前端的方式,将选择下来的数据保存在前端方便回溯使用。
- 使用
EChart使用图表功能。
25.10.19
- 修改界面的筛选功能相互独立,切换每种选择代表重新打开该页面同时选择该模式。
25.10.20
vue3中监视变量可以用watch和watchEffect两个接口实现。前者需明确指定监视源,非自动追踪所有依赖:后者可以实现自动追踪依赖,自动追踪函数内部使用的所有响应式变量,当这些变量变化时自动触发函数。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35<template>
<div>
<p>count: {{ count }}</p>
<button @click="count++">+1</button>
</div>
</template>
<script setup>
import { ref, watch } from 'vue'
// 1. 监视 ref 变量
const count = ref(0)
// 监视 count 的变化,回调函数接收新值和旧值
watch(count, (newVal, oldVal) => {
console.log(`count变化了:旧值=${oldVal},新值=${newVal}`)
// 这里写处理逻辑(比如请求接口、更新其他变量等)
})
// 若监视的是reactive对象的某个属性,则用getter函数
const user = reactive({
name: '张三',
age: 18
})
// 监视 user.age 的变化(用getter函数指定具体属性)
watch(
() => user.age, // 明确监视的源:user.age
(newAge, oldAge) => {
console.log(`年龄变化:旧=${oldAge},新=${newAge}`)
}
)
// 还可以额外增加选项(immediate/deep)
// immediate: true:初始化时立即执行一次回调(默认不执行)
// deep: true:深度监视(当监视对象 / 数组内部属性变化时需要)
</script>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19<script setup>
import { ref, watchEffect } from 'vue'
const count = ref(0)
const name = ref('')
// 自动追踪函数内用到的响应式变量(count 和 name)
watchEffect(() => {
console.log(`当前count: ${count.value},name: ${name.value}`)
// 当 count 或 name 变化时,会重新执行此函数
})
// 其会返回一个停止函数,调用后可停止监视
const stop = watchEffect(() => {
console.log('count:', count.value)
})
// 比如3秒后停止监视
setTimeout(stop, 3000)
</script>vue3中有undefined可以用来通过属性值的比较来判断该变量是否被定义。- 打印可以打印出
null,并且这个null变量search_id还能进入这个if isinstance(search_id, str) and search_id != "" and search_id is not None分支的原因是?可能是因为1.从 JSON 数据中解析的值(JSON 中的 null 若被错误处理,可能会被转换成字符串 “null”,而非 Python 的 None);2.前端传递的参数(比如用户输入或接口返回的字符串 “null”);很神奇的问题。
25.10.21
- vue3中如何判断某个对象是否为数组类型?优先使用
Array.isArray()来判断,简洁且无兼容性问题。 - 对于
Promise对象的处理,推荐使用async/await或者.then()语法来简化异步代码的编写和阅读。
25.10.22
- lyk提出修改筛选条件为填写后自动补充。
- 如何在vue3框架下实现对特定数据的饼图和柱状图呈现?
25.10.23
中justify属性的作用是控制行内子元素通常为 的水平对齐方式 ,其中,start:左对齐,end:右对齐,center:居中对齐;space-around:每个子元素两侧间距相等,均匀分布;space-between:子元素之间间距相等,首尾元素与容器边缘对齐。- 事件处理函数的参数是由触发事件的组件(这里是 el-table)自动传递的,不需要手动显式传入。在 Element Plus 的
组件中,@row-click 是一个 内置事件,当用户点击表格的某一行时,组件会自动触发这个事件,并 主动传递三个参数 给事件处理函数:
row:当前点击行的数据(即该行对应的完整数据对象);column:当前点击的列信息(可选);event:原生点击事件对象(可选)。 - 只要不互斥就只使用柱状图(使用比例的方式),否则使用饼图+柱状图。
25.10.26
- 性别、民族、鼻炎、血泪、泪道冲洗情况做饼图和柱图,诊断、手术方式和人工泪管类型只做柱图。
- 如何给一个不定长的数组中的每一个元素赋予比较有对比意义的颜色用于饼图的呈现?通过固定颜色主色并辅以衍生色循环来覆盖任意长度的数组。
- 对于{“汉族”: 4,”壮族”:5,”size”:9}此类数据类型,如何在typescript中遍历其中除了”size”外的键值对?首先先通过数据类型的限定来过滤掉不包含的目标键,再通过
for(const [key, value] of entries)的方式进行遍历。
25.10.28
nginx部署方式,通过修改/etc/nginx/nginx.config等配置文件来实现对于特定端口的监听以及网页的部署,注意部署时一定要注意nginx代理的文件夹的权限,一定要从根目录至该目录都有可读可执行(r和x)权限,不然会报404NOTFOUND的错误。sudo nginx -s reloadnginx代理部署命令。
25.10.29
- 选中集合给个默认显示,默认饼图 bingo
- 修改黑白颜色 bingo
- 泪道相关图title修改为泪道冲洗情况(1月)这样 bingo
- 饼图中去掉比例图三个字
- 饼图中剔除出数量为0的部分 bingo
- 数值统计图中图例一个颜色,图中也一个颜色,去掉箭头,数值(均值和标准差)显式在x坐标值旁
- 病人集合下的集合名那一列将空白部分缩小。
25.11.4
- 选中集合后相应的集合名修改为显示被选中状态 bingo
- 数值统计的线不要和图例线重合 bingo
- 提高页面的空白利用率,去掉vue框架?使用原生html? 通过修改网格参数使左边和右边更靠近网格边界,因为呈现的本质上是一个装有坐标轴的空白网格.
- 集合名按照创建时间倒序排列,后创建的显示在前 bingo
- 修改集合时将筛选条件也填充上 bingo
25.11.5
- template标签中可以添加class属性嘛?1.HTML 原生
<template>标签:允许添加,但默认不生效(需 JS 激活)2.Vue 中的<template>标签:分场景(根模板 vs 内部模板)Vue3支持,2不支持.3.Django 模板中的{% template %}标签:不允许添加 class - 修改选中底色为按钮的底色 bingo
- 显示的数据保留两位小数 bingo
25.11.6
- 饼图上或者其外显示相应比例数值 bingo
- 不同的柱状图使用不同的颜色 bingo
- 后端增加文件和图片的修改接口 bingo
- 重新实现查看接口 bingo(11.15)
- 单眼信息查看只保留其中一只,另一只的信息直接省略不显示 bingo
- 整合lhy的前端查看修改信息工作 bingo(11.15)
25.11.15
- ‘’空串不能转为int型,即
int('')会报错 - 模型定义的时候
__str__一定要返回str类型,不然会触发很匪夷所思的问题 - fastApi的新建有如下属性的表(学校(str),年级(str)、编号(str)、(近距离左眼屈光度表视力,近距离右眼屈光度表视力,左眼电脑验光屈光度,右眼电脑验光屈光度,左眼测量时长、右眼测量时长)(float)、录入时间) bingo(11.16)
25.11.16
- 添加学校信息表,只写get接口。字段有学校id,学校名称,接口按照学校id倒序排序返回 bingo
- 修改get基本信息接口返回的信息按照录入数据库的时间倒序排列
- 查看中要显示id号 bingo
- 其他部位和全身疾病需要展示 bingo
- 手术信息和术后随访信息都是有则挑一个默认选项展示,否则不展示 bingo
- 调整sidebar的顺序 bingo
- 操作权限问题需要调整 bingo
25.11.18
- 给基本信息添加录入时间属性,用于给传出信息进行排序。bingo
- python的数组排序可以使用
sorted和sort,前者生成新的,后者在原数组上修改,可以通过设置key属性规定排序规则,其中key可以是一个比较函数或者一个lamda函数,如:## patients = sorted(patients, key= lambda x: x['time'], reverse=True) patients.sort(key=lambda x: x['time'])
25.11.21
- 后端返回报错信息全部修改为中文 bingo
- 查看的手术信息显示参看前端上传页面的信息进行展示 bingo
25.11.23
- 456,密码:123456,集合的问题:
- 泪小管炎和泪小管断裂的问题(后端返回数据问题?前者是没有判断空字符串的情况,认为某个字段不是”None”即有内容,而该字段的所有值均为泪小管炎,因此导致错误;后者是搞混了or与in运算符的关系:’3’ or ‘7’ in …的结果是’3’ or (‘7’ in …),则是’3’,因此一直都是true,因此永远执行。 bingo
- 人工泪管类型柱状图问题 忘了(patient_id,eye)不是手术信息和术后随访的主键了) bingo
- 泪道冲洗情况除了1月,其他问题检查 bingo
- 术后随访信息选择框居左展示 bingo
- 病人基本信息、附件信息和手术信息更新权限问题
- 病人信息查看处的删除功能 bingo
- 左右眼的选择时样式改成和按钮一致,文字显式全 bingo
- 手术方式和人工泪管类型为空的话不要占用空间 bingo
- Vue 尝试对一个 null 类型的 VNode 执行 unmount(卸载)或 patch(更新)操作,当读取 VNode 的 type 属性时触发报错。结合错误堆栈(patchKeyedChildren、unmountComponent):主要原因应该是:列表渲染(v-for)中存在 null/undefined 元素。解决方案是在塞入information时筛选一下是否为null或者undefined。 bingo
25.11.26
- 删除后分页消失,原因是删除时调用的接口有误,修改为删除后跳转到第0页直接启动监视器进行分页数据的重获取,一定得到的是第1页的数据(后端的实现) bingo
- 查看展示处左右眼都要显示,只不过被选中后的背景颜色需要改变成按钮的颜色 bingo
25.12.11
- 虚拟机的共享文档操作,折腾几天,不如B站上找个视频,接下来给出大致操作流程吧
- 先在虚拟机上对应的虚拟机设置**选项(而非硬件)**中开启共享文件夹,并给出相应的文件夹配置

- 然后再虚拟机中进行相应的挂载操作,详见视频,这里给出用到的命令:
- 在此之前需要再
/mnt/下新建hgfs文件夹sudo /usr/bin/vmhgfs-fuse .host:/ /mnt/hgfs -o allow_other -o uid=1000 -o umask=022 -o gid=1000 - 设置开机自动挂载,在
/etc/fstab中追加命令配置.host:/ /mnt/hgfs fuse.vmhgfs-fuse allow_other,uid=1000,umask=022,gid=1000 0 0 - 然后万事大吉,享用共享文件夹吧
- 在此之前需要再
- 先在虚拟机上对应的虚拟机设置**选项(而非硬件)**中开启共享文件夹,并给出相应的文件夹配置
25.12.15
- 注意get端口不能接受body数据只能接受查询参数和路径参数(?)
25.12.23
- 在
Django中想要通过ip端口+服务器相应文件夹路径的方式来访问存储在服务器上的静态文件,需要在setting.py的配置文件中配置静态文件夹前缀以及相关的静态文件夹路径,例如:IMG_UPLOAD = os.path.join(BASE_DIR, 'static/uploads'STATICFILES_DIRS = (os.path.join(BASE_DIR,'static'),)
25.12.27
- 注意虚拟机内部相等于一部主机,在本地起后端服务需要相应调整本地前端服务的请求地址
26.1.18
- 迁移文件的增加检测当前信息是否已经在新表中存在的功能,防止重复插入,保证迁移的正确进行。
- 修改其他属性的具体内容组织问题,原来的版本会将空的其他属性也加入”//“中,导致出现如下情况”shjkdh////dhjhkj/jdk”这种情况出现。
- 导出和导入虚拟机的教程。
26.2.28
- 帮学长画流向图,发现提供给我的相关数据有省直辖县级行政区划的概念,这些东西直接由省级行政单位进行管理,只有海南、湖北、河南和新疆有,因此导致前面生成的图以及后续生成流向图的时候没办法合理生成,因为这些数据是好几个市共享的数据,除非通过均摊的方式进行,但是这样并不准确。
- 直辖市统一处理成一个市
- 省直辖县级行政区划不进行处理先直接删除
26.3.1
- 去除了湖北和海南两省的省直辖县级行政以及新疆维吾尔族自治区的自治区直辖县级行政,同时统一了所有直辖市的人口流入流出信息。
- 流向的箭头颜色仍为黑色,目前未发现修改的方法
- 有些城市有人口流出但是没有人口流向线的原因应该是由于此处流向图只考虑了人口迁移量大于等于10000的,因此这些城市没有流向线很有可能是因为其所有引出的流向人口数都小于10000。
26.3.2
- 迁移人口数的图例颜色分别为:rgb(255, 244, 229)、rgb(255, 199, 149)、rgb(255, 149, 79)、rgb(255, 79, 29)、rgb(199, 19, 0)。点用城市迁入人口数规模表示,线用迁移量规模表示。

