C项目实践
计算机程序员认证:如C++,需深入理解语言并实践项目经验。 #生活技巧# #工作学习技巧# #技能证书考取攻略#
1.功能需求分析
家庭财务管理系统给家庭成员提供了一个管理家庭财务的平台,系统可以对家庭成员的收入和支出进行增加,删除、修改和查询等操作,并能统计总收入和总支出。其主要功能需求描述如下:
(1)系统主菜单界面: 充许用户选择想要进行的操作,包括输入收入管理、支出管理、统计和退出系统等操作。其中收入管理包括添加收入、查询收入明细、删除收入和修改收入的操作,支出管理包括添加支出、查询支出明细、删除支出和修改支出的操作。统计是对总收入和总支出进行统计操作。
(2)添加收入处理: 用户根据提示,输入要添加的收入信息,包括收入的日期,添加收入的家庭成员姓名、收入的金额以及备注信息。输入完一条收入记录,将其暂时保存在单链表中,然后返回到主菜单。
(3)查询收入明细处理:根据用户输入的年月信息在单链表中查找收入信息,如果查询成功,按照预定格式显示该收入明细。如果没有该数据,则给出提示信息。查询结束后,询问用户是否继续查找,根据用户的输入进行下一步操作。
(4)删除收入处理:首先提示用户输入要删除的信息的年月,根据用户的输入在单链表中查询。如果没有查到任何信息,则给出提示信息。如果查询成功,显示该收入明显,并提示用户输入要删除的信息的对应序号,以删除该信息,用户输入对应的序号就删除相关的收入信息,并给出删除成功的提示信息,用户输入其它按键则重新删除操作。
(5)修改收入处理:首先提示用户输入要进行修改的信息的年月,如果单链表中有该收入信息存在,则提示用户输入要修改的收入日期,家庭成员姓名等信息并将结果重新存储到单链表中。如果没有找到要修改的收入信息,则给出提示信息。
(6)添加支出处理 完成用户支出信息的添加,与添加收入处理类似。
(7)查询支出明细处理 查询支出信息,与查询收入明细信息类似。
(8)删除支出处理,删除支出信息,与删除收入信息处理类似。
(9)修改支出处理,修改支出信息与修改收入信息类似。
(10)统计总收入和总支出处理: 计算单链表中所有的收入总和和支出总和,并计算出家庭收入的结余信息
(11)退出系统。
2.总体设计
2.1功能模块设计
1.添加收入信息
主菜单中选择1 ,进入添加收入操作,系统调用add_income()函数来添加收入信息,首先建立单链表,函数调用input_info()函数提示用户输入收入信息,并存储到单链表中,输入完成后返回到主菜单界面。
2.查询收入明细
主菜单中选择2, 进入查询收入明细操作,系统调用search_income()函数来查询收入的明细,调用search_data()函数来完成具体的查询操作。首先提示用户输入要查询的年月,如果用户输入错误,则给出输入有误提示信息,如果输入正确,在单链表中查找该年月的收入信息,如果查找成功,按照指定格式显示找到的收入信息。每页显示9条收入信息,如果找到收入的信息多于9条,按空格键翻页。如果没有找到任何信息,则给出相应的提示信息。查找成功后提示用户是否继续查找操作,如果用户输入"y"或"Y“, 则再次调用次函数进行相应的操作。否则,直接返回主菜单界面。
3.删除收入信息
主菜单中选择3的时候进行删除收入信息的操作,调用delete_data()函数进行删除收入的操作。首先提示用户输入要删除的信息的所属年月,然后根据输入的年月信息在单链表中查询相关信息,如果查找成功则调用show_info()函数显示查找到的收入信息,每页最多显示9条信息,如果查找到的收入信息多于9条,则按空格键翻页,最后提示用户输入要删除的收入信息的序号,完成删除的操作。如果查找不成功,给出相应的提示信息。删除操作结束后,提示用户是否继续删除的操作,如果用户输入"y"或"Y",则再次调用此函数进行相应的操作,否则返回主菜单界面。
4.更新收入信息
主菜单中选择4,进入更新收入信息的操作,调用update_data()函数进行更新收入信息的操作。首先,提示用户输入要更新收入所属年月,然后根据用户输入的年月信息在单链表中查找相关信息。如果查找到的收入信息多于9条,则按空格键翻页,最后提示用户输入要更新的收入信息的序号,调用inut_info()函数,输入要更新的收入信息,完成更新的操作。如果查找不成功,则给出相应的提示信息。更新操作结束后,提示用户是否继续进行更新操作,如果用户输入"y"或"Y", 则再次调用本函数进行更新操作,否则,返回主菜单界面。
5.添加支出信息
主菜单中选5, 进入添加支出信息操作。系统调用add_income()函数来添加支出信息。添加支出信息的操作和添加收入信息的操作相似。
6.查询支出明细
主菜单中选6,进入查询支出明细操作,系统调用search_income()函数来查询支出明细操作。调用search_data()函数来完成具体的查询操作。查询支出明细的操作与查询收入明细的操作相似。
7.删除支出信息
主菜单选择7,进入删除支出信息操作,调用delete_data()函数进行删除支出的操作。删除支出操作与删除收入操作相似。
8.更新支出信息
主菜单中选择8,进入更新支出信息操作。调用update_data()函数进行更新支出信息操作。具体操作过程与更新收入信息操作相似。
9.统计总收入,总支出
主菜单中选择9,进入统计操作。调用count_total()函数进行总收入,总支出的统计。在单链表中,计算收入和支出的总和,并将两者相减得到家庭收入的结余,并按相应格式显示出来。统计结束之后,按任意键返回主菜单界面。
10.退出系统
主菜单中选择10,进入退出系统操作。调用quit()函数进行退出操作,首先调用save_to_file()函数,将单链表中的数据保存到文件,再调用clear_data()函数清空单链表,最后退出系统。
2.2系统处理流程
系统执行应从主菜单的选择开始,充许用户输入0-9之间的数值来选择要进行的操作,输入其它字符都是无效的,系统会给出出错的提示信息。若用户输入0,调用quit()函数退出系统;
若输入1,则调用add_income()函数,进行添加收入操作;
若输入2,则调用search_income()函数,进行查询收入明细;
若输入3,则调用delete_income()函数,进行删除收入操作;
若输入4,则调用update_income()函数,进行修改收入操作;
若输入5,则调用add_payout()函数,进行添加支出操作;
若输入6,则调用search_payout()函数,进行查询支出明显;
若输入7,则调用delete_payout()函数,进行删除支出操作;
若输入8,则调用update_payout()函数,进行更新支出操作;
若输入9,则调用count_total()函数,进行统计总收入,总支出操作。
1.添加操作
首先建立单链表,调用input_info()函数,提示用户输入要添加的收入或支出信息,并将输入信息存储到单链表中,输入完成之后返回主菜单界面。
2.查询操作
首先提示用户输入要查询的年月,如果用户输入错误,给出相应的提示信息;如果用户输入正确,则根据输入的数据在单链表中查找收入或支出的信息。如果没有找到,则给出没有数据的提示信息,如果找到,则判断记录个数是否小于9,如果小于9,直接显示结果,否则提示用户按空格键翻页。查询操作的处理流程图如下所示:
3.删除操作
首先提示用户输入要删除的年月,如果用户输入错误,给出相应的提示信息,如果输入正确,则在单链表中查询收入或支出的信息,如果没有找到,给出没有数据的提示信息,如果找到,显示找到的记录信息,并提示用户输入对应的序号进行删除操作。删除成功之后,给出提示信息。删除操作的处理流程如图:
4.修改操作
首先提示用户输入要查询的年月,如果输入错误,给出相应的提示信息,如果输入正确,则先在单链表中查找收入或支出的信息,如果没找到,则给出提示信息,否则显示找到的记录信息,并提示用户输入序号,进行修改,用户可以根据提示信息输入要修改的收入或支出信息。修改操作的处理流程如图:
2.3详细设计与系统实现
打开vs, 新建win32 console Application ,选择Empty Proj , 点击Finished 完成FinanceMng项目创建
在Header Files 和 Source Files 分别新建 FinanceMain.h 和 FinanceMain.cpp 文件,为了便于维护和逻辑清晰,我们把函数及变量的声明部分放在FinanceMain.h中进行,在FinanceMain.cpp中实现相应的功能模块。下面进入FinanceMain.h文件对系统要用到的函数及变量进行声明,首先包含要用的头文件:
//Header Information
#include <iostream>
家庭财务管理系统主要是对家庭成员的基本信息及财务收支信息的管理,其中涉及到信息我们将它们归类并希望通过自定义三个数据类型来集中管理和使用这些信息,那么其中有些字段由于其本身的需求,最好定义为字符数组来处理,为了具有可扩展性和便于更好的维护,需要先定义一些常量宏,这样当这些字段的大小发生变化时,可以通过简单的修改这些常量宏来达到目的,这其中包括定义 家庭成员姓名的最大长度、收支信息记录的备注最大长度、菜单选项个数以及数据文件名等常量宏的定义,具体定义如下:
//Macro constant
#define MAX_NAME 11 //家庭成员姓名最大长度
#define MAX_DETAIL 21 // 备注最大长度
#define MENU_COUNT 9 //菜单选项个数
#define DATA_FILE "fs.dat" //数据文件文件名
常量宏定义好之后,接下来就开始来声明三个自定义的数据类型,首先本系统目前主要实现记录家庭成员的支出和收入两大类财务信息,但不确定今后是否需要加入其它类的财务信息的记录,所以最好把它们独立自定义一个数据类型,但是这里需要注意的是,这个数据类型它的成员只有有限的几种值的选择,在目前来看它的值不是代表收入就是支出,无第三种值可以选择, 所以最好定义为一个枚举类型,具体实现如下:
//DataStruct Definition
//自定义枚举类型 fi_type , 用来表示收入支出
typedef enum _fi_type
{
income = 1, //收入
payout = -1 //支出
}fi_type;
接下来定义一个存储家庭财务信息的结构体fi_info
typedef struct _fi_info
{
int year;
int month;
fi_type type; //数据类型,即财务类型是收入还是支出
char name[MAX_NAME]; //家庭成员姓名,属于TA的收入或由TA支出
float money; //收入或支出的金额
char detail[MAX_DETAIL]; //备注信息
}fi_info;
还需要定义一个存储财务数据结构的结构体fi_data, 通过此结构把财务数据连接成链表,具体实现如下:
typedef struct _fi_data
{
fi_info info; //财务数据
struct _fi_data* next; //指向下一个节点的指针
}fi_data;
fi_data* head = NULL; //定义单链表头节点, 初始化为NULL
接下来对本系统要使用到的函数进行声明,首先来声明主菜单对应的处理函数
//Functions Definition
void add_income(); //添加收入
void search_income(); //查询收入明细
void delete_income(); //删除收入
void update_income(); //修改收入
void add_payout(); //添加支出
void search_payout(); //查询支出明细
void delete_payout(); //删除支出
void update_payout(); //修改支出
void count_total(); //统计总收支情况
void quit(); //退出系统
财务系统中主要进行的操作是进行查询,删除和更新操作,这三个操作在多个功能模块中都将被用到,所以把它们独立声明为函数,供其它函数调用,具体声明如下:
//Main processing Function
void search_data(fi_type type); //查询处理
void delete_data(fi_type type); //删除处理
void update_data(fi_type type); //更新处理
为了使得项目代码更加简洁和逻辑清晰可控,我们把一些非核心的公共部分抽象出来成为系统的辅助函数予以声明处理:
//Auxiliary Function
void initialize(); //系统初始化,加载数据
void save_to_file(); //将财务数据保存到文件中
void clear_data(); //清空链表中数据
fi_data* get_last(fi_data* p); //得到财务数据链表的最后一个节点
fi_data* get_previous(fi_data* p); //取得财务数据节点p的前驱节点
void input_info(fi_info* info); //输入财务数据信息
void show_info(fi_data* p[],int count); //显示财务数据
接下来将主菜单界面定义为一个常量字符串数组:
//constant
char menu[] =
"====================================================\n"
"| 家庭财务管理系统 |\n"
"+--------------------------------------------------+\n"
"| 收入管理 |\n"
"| <1> 添加收入 |\n"
"| <2> 查询收入明细 |\n"
"| <3> 删除收入 |\n"
"| <4> 修改收入 |\n"
"| 支出管理 |\n"
"| <5> 添加支出 |\n"
"| <6> 查询支出明细 |\n"
"| <7> 删除支出 |\n"
"| <8> 修改支出 |\n"
"| 统计 |\n"
"| <9> 统计总收入总支出 |\n"
"+--------------------------------------------------+\n"
"| 输入 <0> 退出系统 |\n"
"+--------------------------------------------------+\n";
最后为了能够根据用户输入的数值,方便地调用相应的函数进行处理,还需要定义一个函数指针数组用来对应相应的操作函数,具体声明如下:
void (*menu_func[])() =
{
quit, //退出系统
add_income, //添加收入
search_income, //查询收入明细
delete_income, //删除收入
update_income, //修改收入
add_payout, //添加支出
search_payout, //查询支出明细
delete_payout, //删除支出
update_payout, //更新支出
count_total //统计收支结余
};
现在基本上已经把要用到的函数和变量在FinanceMain.h文件中进行了声明,如果需求变更则可再来修改即可。下面进入FinanceMain.cpp文件中来实现相应的函数功能,首先应当在FinanceMain.cpp文件开始处包含FinanceMain.h头文件:
#include FinanceMain.h
系统的执行应从主菜单处开始,所以首先来实现main入口函数,将主菜单打印出来,并等待用户输入相应的操作选项,如果输入有误,则给出提示信息,并提示重新输入, 直到输入正确为止。具体实现如下:
//Entry Function
int main()
{
//选择标识符,表明用户选择的操作,初始化为-1,表示没有选择任何选项
int selected = -1;
initialize(); //加载数据,初始化系统
while(1)
{
system("cls");//清屏
printf(menu); //打印主菜单
printf(">请选择要进行的操作[%d-%d]:",0,MENU_COUNT);
if(scanf("%d",&selected)!=1 || selected <0 || selected > MENU_COUNT)
{
printf(">输入有误!请选择[%d-%d]之间的数字!按任意键继续...",0,MENU_COUNT);
fflush(stdin);
getchar();
}else{
menu_func[selected]();
}
}
return (0);
}
接下来将对主菜单中对应的处理函数一一进行实现
1.添加收入
函数名称:add_income
函数功能:用来添加收入信息的操作。
处理过程:首先建立单链表,调用input_info()函数,提示用户输入收入信息,并将输入的信息存储到单链表中,输入完成后返回到主菜单界面。具体实现如下:
//添加收入
void add_income()
{
//定义一个存储财务数据的结构体指针p ,并申请内存
fi_data* p = (fi_data*)malloc(sizeof(fi_data));
if(NULL == p) throw printf("内存申请失败...\n"); //如果内存申请失败则抛出异常
memset(p,0,sizeof(fi_data)); // 置空
p->next = NULL;
input_info(&(p->info)); //录入相应的财务数据信息
p->info.type = income; //财务类型标识为收入
//如果当前头指针为空,表示当前单链表为空,则把p作为头节点并将地址赋值给head
if(NULL == head)
{
head = p;
}else{ //否则寻找当前单链表的最后一个节点并把p连接到这个节点后面
get_last(head)->next = p;
}
}
因为查询,删除和修改信息在收入管理中的查询收入明细、删除收入信息、更新收入信息这三项操作和支出管理中的查询支出明细、删除支出以及更新支出等三项操作操作几乎相同,仅仅只是财务数据类型不同,所以我们直接把查询、删除和修改信息这三项独立出来,写成公共模块,来供收入管理或支出管理需要使用时作相应的调用即可,即避免代码冗余,亦便于维护。
2.查询收入明细
函数名称:search_income
函数功能:用来查询收入明细的操作。函数中调用search_data()来完成收入明细的查询。具体实现如下:
//查询收入明细
void search_income()
{
search_data(income);
}
3.删除收入
函数名称:delete_income
函数功能:用来删除收入信息的操作,在函数中调用delete_data()来完成收入信息的删除,具体实现如下:
//删除收入信息
void delete_income()
{
delete_data(income);
}
4.修改收入
函数名称:update_income
函数功能:用来修改收入信息的操作,通过调用update_data()函数来完成收入信息的修改,具体实现如下:
//更新收入信息
void update_income()
{
update_data(income);
}
5.添加支出信息
函数名称:add_payout
函数功能:添加支出信息的操作
处理过程:首先建立单链表、调用input_info()函数提示用户输入支出信息,并将输入信息存储在单链表中,输入完成后返回到主菜单界面。实现过程和添加收入雷同,只是录入的财务类型为支出。具体实现如下:
//添加支出信息
void add_payout()
{
fi_data* p = (fi_data*)malloc(sizeof(fi_data));
memset(p,0,sizeof(fi_data));
p->next = NULL;
input_info(&(p->info));
p->info.type = payout;
if(NULL == head)
{
head = p;
}else{
get_last(head)->next = p;
}
}
6.查询支出明显
函数名称:search_payout
函数功能:用来查询支出明细操作,通过调用search_data()来实现,具体实现如下:
//查询支出信息明显
void search_payout()
{
search_data(payout);
}
7.删除支出
函数名称:delete_payout
函数功能:通过调用delete_data()函数来完成删除支出信息的操作,具体实现如下:
//删除支出信息
void delete_payout()
{
delete_data(payout);
}
8.修改支出
函数名称:update_payout
函数功能:通过调用update_data()来修改支出信息,具体实现如下:
//更新支出信息
void update_payout()
{
update_data(payout);
}
9.统计总收支结余信息
函数名称:count_total
函数功能:在单链表中,分别计算出收入和支出的总和,并将两者相减算出结余信息,具体实现如下:
//统计总收支结余
void count_total()
{
float total_income = 0.0;
float total_payout = 0.0;
fi_data* p = head;
while(NULL != p)
{
if(p->info.type == income)
{
total_income += p->info.money;
}else{
total_payout += p->info.money;
}
p = p->next;
}
printf("\n+------------+------------+------------+\n");
printf("| 合计收入 | 合计支出 | 结余 |\n");
printf("|%12.2f|%12.2f|%12.2f|\n",
total_income,total_payout,total_income - total_payout);
printf("+------------+------------+------------+\n");
printf(">按任意键返回主菜单...");
fflush(stdin);
getchar();
}
10.退出系统
函数名称: quit
函数功能:将单链表中的数据先保存到本地磁盘文件中,然后清空单链表中的数据,最后退出系统。具体实现如下:
//退出系统
void quit()
{
save_to_file();
clear_data();
exit(0);
}
接下来对前面抽象出来的查询、删除和修改三个处理函数进行实现
1.查询处理
函数名称:search_data
函数功能:收入和支出的查询操作。
处理过程:
(1)提示用户按照指定格式输入要查询的年月,如果用户输入错误,则给出提示信息;如果输入正确,则在单链表中查找该年月的信息
(2)如果查找成功,则调用show_info()函数显示找到的信息,并判断查询结果是否小于9条,如果大于9条信息,则提示按空格键进行翻页操作。
(3)如果没有找到符号年月的信息,则给出提示信息
(4)提示用户是否继续进行查询操作,如果用户输入"y"或"Y", 则再次调用本函数进行处理,否则返回主菜单界面。具体实现如下:
//查询处理
void search_data(fi_type type)
{
int year = 0,
month = 0;
//定义单链表节点p
fi_data* p = NULL;
//定义单链表 指针数组result,用来记录查询到符合添加的信息的地址
fi_data* result[9] ={NULL};
//定义记录查询到的数据条数的变量 count 初始化为0
int count = 0;
//定义记录用户输入的选择,是否继续进行查询操作
char input = ' ' ;
while(1)
{
printf(">请输入要查询的年月(例如:2009/1)");
fflush(stdin);
if(scanf("%d/%d",&year,&month) !=2) //*%d%d ->%d/%d
{
printf(">输入有误!\n");
}else{
p = head;//将p指向单链表头节点
count = 0; //将查询到的记录清零
//将result 指针清零
memset(result,0, sizeof(fi_data*));
while(NULL != p)
{
if(p->info.year == year &&
p->info.month == month &&
p->info.type == type)
{
if(count <9)
{ //如果当前查询到的记录信息不足9条,则
//直接把查询到的记录保存到result指针数组中,
//然后count 加1
result[count] = p;
count++;
}else{
//如果查询结果大于9条记录,则先调用show_info()显示这9条信息
//并提示用户按空格键进行翻页操作
show_info(result,count);
printf(">输入空格并回车翻页,按其它键退出....");
fflush(stdin);
input = getchar();
if(input == ' ')
{ //如果用户输入了空格键,则先清空当前result数组保存的记录地址
//然后将记录条数的变量count置0,
//把当前查询到的第10条记录先保存到result中,
//然后重新开始count加1
memset(result , 0, sizeof(fi_data*));
count = 0;
result[count] = p;
count++;
}else{
//如果用户没有输入空格键进行翻页操作,则直接跳出,返回主界面
break;
}
}//end else
}//endif
//进入下一条记录的比对查找,直到查询完整个单链表为止(即 p == NULL 为止)
p = p->next;
}//end while loop
//如果count 不等0,此时肯定也没有大于9 ,则直接调用show_info()显示信息
if(0 != count)
{
show_info(result,count);
}else{
//count == 0 则提示没有查询到符合添加的数据
printf(">没有找到数据...\n");
}
//至此一次完整的查询操作结束, 提示用户是否继续进行查询操作
printf(">继续查找其它数据?(Y or N)");
fflush(stdin);
input = getchar();
if(input == 'y'||input == 'Y')
{
continue;
}else{
break;
}
}
}
}
2.删除操作
函数名称:delete_data
函数功能:收入与支出的删除操作
处理流程:
(1)提示用户输入要删除的收支信息所属年月,然后根据用户输入的年月信息在单链表中查找相关信息。
(2)如果查找成功,且查找的记录条数小于9条,则直接调用show_info()函数显示查找到收支信息,如果查找的记录条数大于9,则调用show_info()函数将当前查询到的9条信息显示出来,并提示用户按空格键进行翻页操作。
(3)最后提示用户输入要删除的的收支信息的序号,完成删除操作。
(4)如果查找不成功,则给出相应的提示信息
(5)删除完成之后,提示用户是否继续进行删除操作,如果用户输入"y"或"Y", 则继续进入删除操作界面,否则返回主菜单界面。具体实现如下:
//删除操作
void delete_data(fi_type type)
{
int year = 0,
month = 0;
//定义一个单链表节点p, 用来标识当前节点位置
fi_data* p = NULL;
//定义一个单链表节点pre ,用来标识当前节点的前驱节点
fi_data* pre = NULL;
//定义一个指针数组用来保存当前查询到的记录信息的地址
fi_data* result[9] = {NULL};
//定义一个记录信息的变量,记录当前查询到的符合条件的条数
int count = 0;
//用来标识用户输入的选择,是否判断是否继续进行删除操作
char input = ' ';
//定义一个变量i,
int i = 0;
while(1)
{
printf(">请输入要查询的年月(例如:2009/1):");
fflush(stdin); //清空缓冲区,以免造成死循环!
if(scanf("%d/%d",&year,&month) != 2) //* %d%d->%d/%d
{
printf(">输入有误!\n");
}else{
p = head; //p指向头节点
count = 0; //记录信息条数清零
//清空result中的记录信息
memset(result, 0, sizeof(fi_data*));
while(NULL != p)
{
if(p->info.year == year &&
p->info.month == month &&
p->info.type == type)
{
if(count< 9)
{
result[count] = p;
count++;
}else{
show_info(result,count);
printf(">输入空格并回车翻页.输入对应的序号删除.其它键退出.请输入:");
fflush(stdin);
input = getchar();
if(input == ' ')
{ //输入空格键,继续查询符合条件的记录,并清空result数组信息,
//同时将记录条数count归零处理, 并此次查询到的符合条件的信息保存到result中
memset(result, 0, sizeof(fi_data*));
count = 0;
result[count] = p;
count++;
}else if(input >= '1' && input <= 48 + count){
//用户键入选择删除的记录信息的对应序号
i = input -49;
//获取当前节点的前驱节点
pre = get_previous(result[i]);
if(NULL ==pre)//如果前驱节点为空,则表明当前节点为头节点
{ //删除头节点
head = head->next;
}else{
//否则删除当前节点
pre->next = result[i]->next;
}
//释放掉当前节点所占内存
free(result[i]);
//将result数组中i节点后面的节点前移动一位
网址:C项目实践 https://www.yuejiaxmz.com/news/view/540377
相关内容
C语言小项目实践——日历程序【物联网】物联网智能项目:从概念到实践
“垃圾变有机”家庭环保实践项目
东华大学2024年大学生创业训练、创业实践项目立项公示
蒙牛环保公益让爱循环 优益C“春雨新生”再生校服项目圆满收官
试论中小学实施跨学科项目式学习的必要性
生活的实践性与教育的实践立场
C#实现个人理财软件小项目
DIY创意手工艺品项目的市场调查与实现路径研究
文明实践项目 | 社区微改造 营造美好社区生活